'use client'; import { useState, useEffect } from 'react'; import { ArrowLeft, Check, ChevronRight, Loader2 } from 'lucide-react'; import { useRouter } from 'next/navigation'; type PlanType = 'trial' | 'start' | 'plus' | 'max'; type Period = '1month' | '3months' | '6months' | '1year'; type Step = 'plan' | 'period' | 'locations' | 'processing'; interface Location { id: number; name: string; address: string; ping: string; country: string; flag: string; status: string; } interface PlanConfig { name: string; badge: string; maxLocations: number; dataLimit: string; prices: Record; } const PLANS: Record = { trial: { name: 'Пробный', badge: '🎁 Пробный период', maxLocations: 999, dataLimit: 'Безлимит', prices: { '1month': 0, '3months': 0, '6months': 0, '1year': 0, }, }, start: { name: 'Старт', badge: '🌍 Старт', maxLocations: 1, dataLimit: '50 ГБ', prices: { '1month': 99, '3months': 269, // -9% (89₽/мес) '6months': 499, // -16% (83₽/мес) '1year': 949, // -20% (79₽/мес) }, }, plus: { name: 'Плюс', badge: '🌎 Плюс', maxLocations: 3, dataLimit: '299 ГБ', prices: { '1month': 249, '3months': 649, // -13% (216₽/мес) '6months': 1199, // -20% (199₽/мес) '1year': 2249, // -25% (187₽/мес) }, }, max: { name: 'Макс', badge: '🌏 Макс', maxLocations: 0, // Все локации автоматически, без выбора dataLimit: 'Безлимит', prices: { '1month': 350, '3months': 949, // -10% (316₽/мес) '6months': 1799, // -14% (299₽/мес) '1year': 3349, // -20% (279₽/мес) }, }, }; const PERIOD_LABELS: Record = { '1month': { label: '1 месяц', months: 1 }, '3months': { label: '3 месяца', months: 3 }, '6months': { label: '6 месяцев', months: 6 }, '1year': { label: '1 год', months: 12 }, }; export default function PlansNew() { const router = useRouter(); const [step, setStep] = useState('plan'); const [selectedPlan, setSelectedPlan] = useState(null); const [selectedPeriod, setSelectedPeriod] = useState('1month'); const [selectedLocations, setSelectedLocations] = useState([]); const [availableLocations, setAvailableLocations] = useState([]); const [isLoadingLocations, setIsLoadingLocations] = useState(false); const [isCreatingUser, setIsCreatingUser] = useState(false); const [referrerId, setReferrerId] = useState(null); // Получаем referrerId из Telegram start_param при монтировании useEffect(() => { const telegramWebApp = (window as any).Telegram?.WebApp; const startParam = telegramWebApp?.initDataUnsafe?.start_param; if (startParam && startParam.startsWith('ref_')) { const refId = startParam.replace('ref_', ''); setReferrerId(refId); console.log('🎁 Referral detected:', refId); } }, []); // Загрузить локации при переходе к шагу выбора useEffect(() => { if (step === 'locations' && availableLocations.length === 0) { loadLocations(); } }, [step]); async function loadLocations() { setIsLoadingLocations(true); try { const response = await fetch('/api/nodes'); const data = await response.json(); if (data.success) { setAvailableLocations(data.locations || []); } else { console.error('Failed to load locations:', data.error); } } catch (error) { console.error('Error loading locations:', error); } finally { setIsLoadingLocations(false); } } function handlePlanSelect(plan: PlanType) { setSelectedPlan(plan); // Trial период - сразу создаем без выбора периода и локаций if (plan === 'trial') { createUser(plan, '1month', []); } else { setStep('period'); } } function handlePeriodSelect(period: Period) { setSelectedPeriod(period); // Только для PLUS показываем выбор локаций // Для остальных тарифов - все локации автоматически if (selectedPlan === 'plus') { setStep('locations'); } else { // Start и Max - создаем сразу со всеми локациями createUser(selectedPlan!, period, []); } } function handleLocationToggle(locationId: number) { if (!selectedPlan) return; const maxLocations = PLANS[selectedPlan].maxLocations; if (selectedLocations.includes(locationId)) { setSelectedLocations(selectedLocations.filter(id => id !== locationId)); } else if (maxLocations === 999 || selectedLocations.length < maxLocations) { setSelectedLocations([...selectedLocations, locationId]); } } async function createUser(planType: PlanType, period: Period, locationIds: number[]) { setStep('processing'); setIsCreatingUser(true); try { const telegramWebApp = (window as any).Telegram?.WebApp; const user = telegramWebApp?.initDataUnsafe?.user; console.log('🔍 TELEGRAM USER DATA:', { hasWebApp: !!telegramWebApp, hasInitData: !!telegramWebApp?.initDataUnsafe, hasUser: !!user, user: user ? { id: user.id, username: user.username, first_name: user.first_name, last_name: user.last_name, } : null }); // 🔓 ИСКЛЮЧЕНИЕ: Проверяем переход из n8n const urlParams = new URLSearchParams(window.location.search); const source = urlParams.get('source') || urlParams.get('utm_source'); const fromN8n = source === 'n8n' || source === 'chat' || urlParams.has('from_n8n'); // ⚠️ ВАЖНО: Если Telegram не передал user.id И это не n8n переход - блокируем if (!user?.id && !fromN8n) { console.error('❌ USER CREATION BLOCKED: No Telegram user ID!'); alert('❌ Откройте приложение через Telegram бота\n\nПриложение должно быть открыто в Telegram Mini App, а не в браузере.'); setStep('plan'); setIsCreatingUser(false); return; } // Для n8n переходов генерируем временный ID const userId = user?.id || Date.now(); const requestBody = { planType, period, locationIds, telegramId: userId, // Telegram ID или временный для n8n telegramUsername: user?.username || null, firstName: user?.first_name || null, lastName: user?.last_name || null, referrerId: referrerId || null, source: fromN8n ? (source || 'n8n') : null, // Помечаем n8n переходы }; console.log('📤 REQUEST BODY:', requestBody); const response = await fetch('/api/create-user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody), }); const data = await response.json(); if (data.success) { // Успех - возвращаемся на главную с параметром обновления router.push('/?refresh=true'); } else { throw new Error(data.error || 'Failed to create subscription'); } } catch (error) { console.error('Create user error:', error); alert('Ошибка при создании подписки. Попробуйте позже.'); setStep('plan'); } finally { setIsCreatingUser(false); } } function handlePurchase() { if (!selectedPlan || selectedLocations.length === 0) { alert('Выберите хотя бы одну локацию'); return; } createUser(selectedPlan, selectedPeriod, selectedLocations); } function calculateDiscount(period: Period): number { const discounts = { '1month': 0, '3months': 10, '6months': 15, '1year': 20, }; return discounts[period]; } function calculateSavings(plan: PlanType, period: Period): number { if (plan === 'trial') return 0; const config = PLANS[plan]; const monthlyPrice = config.prices['1month']; const months = PERIOD_LABELS[period].months; const fullPrice = monthlyPrice * months; const discountedPrice = config.prices[period]; return fullPrice - discountedPrice; } function goBack() { if (step === 'period') { setStep('plan'); } else if (step === 'locations') { setStep('period'); } else if (step === 'processing') { setStep('locations'); } } const canProceedFromLocations = selectedPlan && (PLANS[selectedPlan].maxLocations === 999 || selectedLocations.length >= PLANS[selectedPlan].maxLocations); return (
{/* Header */}
{step === 'plan' ? ( ) : step !== 'processing' && ( )}

{step === 'plan' && 'Выбор тарифа'} {step === 'period' && `Период подписки${selectedPlan ? ` · ${PLANS[selectedPlan].name}` : ''}`} {step === 'locations' && 'Выбор локаций'} {step === 'processing' && 'Создание подписки...'}

{/* Шаг 1: Выбор тарифа */} {step === 'plan' && (
{/* Trial - полная ширина */} handlePlanSelect('trial')} /> {/* Grid 2x2 для остальных */}
{(['start', 'plus', 'max'] as PlanType[]).map((planKey) => { const plan = PLANS[planKey]; return ( handlePlanSelect(planKey)} /> ); })} {/* Empty slot */}
💰
Скоро новые
тарифы
)} {/* Шаг 2: Выбор периода */} {step === 'period' && selectedPlan && (
{/* Информация о тарифе */}
Выбран тариф
{PLANS[selectedPlan].name}
Локации
{selectedPlan === 'plus' ? '3 на выбор' : 'Все'}
Трафик
{PLANS[selectedPlan].dataLimit}
{/* Grid 2x2 для периодов */}
{Object.entries(PERIOD_LABELS).map(([periodKey, { label, months }]) => { const period = periodKey as Period; const price = PLANS[selectedPlan].prices[period]; const discount = calculateDiscount(period); const savings = calculateSavings(selectedPlan, period); const isRecommended = period === '6months'; // Самый выгодный return ( ); })}
)} {/* Шаг 3: Выбор локаций */} {step === 'locations' && selectedPlan && (
{PLANS[selectedPlan].maxLocations === 999 ? 'Доступны все локации' : `Выберите до ${PLANS[selectedPlan].maxLocations} локаций`}
{isLoadingLocations ? (

Загрузка локаций...

) : (
{availableLocations.map((location) => { const isSelected = selectedLocations.includes(location.id); const isDisabled = !isSelected && PLANS[selectedPlan].maxLocations !== 999 && selectedLocations.length >= PLANS[selectedPlan].maxLocations; return ( ); })}
)} {selectedLocations.length > 0 && ( )}
)} {/* Шаг 4: Processing */} {step === 'processing' && (

Создаем вашу подписку...

Это займет несколько секунд

)} {/* Spacer для нижней навигации */}
); } // PlanCard Component function PlanCard({ badge, title, price, features, buttonText, isPrimary = false, isPopular = false, onSelect, }: { badge: string; title: string; price: string; features: string[]; buttonText: string; isPrimary?: boolean; isPopular?: boolean; onSelect: () => void; }) { return (
{isPopular && (
Популярный
)}
{badge}

{title}

{price}

    {features.map((feature, index) => (
  • {feature}
  • ))}
); }