From 6cb93359555613fb07da123868ead9232dfbe344 Mon Sep 17 00:00:00 2001 From: Umbrix Dev Date: Thu, 5 Feb 2026 12:11:55 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Setup=20Wizard:=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=88=D0=B0=D0=B3=D0=BE=D0=B2=D0=B0=D1=8F=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B0=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Выбор устройства (💻 Компьютер / 📱 Телефон) - Проверка наличия приложения - Выбор локаций для Extended тарифа (3 из списка) - Показ ссылки/QR кода - Автоматическое открытие после активации Trial - Прогресс бар для отслеживания шагов --- app/page.tsx | 57 ++++- components/SetupWizard.tsx | 432 +++++++++++++++++++++++++++++++++++++ 2 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 components/SetupWizard.tsx diff --git a/app/page.tsx b/app/page.tsx index 3b24231..0e25333 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -9,6 +9,7 @@ import { useRouter } from 'next/navigation'; import Link from 'next/link'; import QRCodeModal from '@/components/QRCodeModal'; import ReferralModal from '@/components/ReferralModal'; +import SetupWizard from '@/components/SetupWizard'; import { marzbanApi } from '@/lib/marzban-api'; import { getSubscriptionUrl, MARZBAN_SUBSCRIPTION_URL } from '@/lib/constants'; import { @@ -41,6 +42,7 @@ export default function Home() { const [isKeyMenuOpen, setIsKeyMenuOpen] = useState(false); const [isQROpen, setIsQROpen] = useState(false); const [isReferralOpen, setIsReferralOpen] = useState(false); + const [isSetupWizardOpen, setIsSetupWizardOpen] = useState(false); const [subscriptionStatus, setSubscriptionStatus] = useState<'active' | 'expired' | 'none'>('none'); const [expiryDate, setExpiryDate] = useState(''); const [showToast, setShowToast] = useState(false); @@ -168,6 +170,45 @@ export default function Home() { copyToClipboard(shareText); } }; + + const handleActivateTrial = async () => { + setIsLoading(true); + try { + // Создаем trial подписку через API + const response = await fetch('/api/create-user', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + planType: 'trial', + telegramId: Date.now(), // Временно, пока нет настоящего Telegram ID + telegramUsername: 'demo_user', + firstName: 'Demo', + }), + }); + + const data = await response.json(); + + if (data.success) { + // Сохраняем токен + setSubscriptionToken(data.token); + setHasSubscription(true); + setSubscriptionStatus('active'); + setExpiryDate(data.expiryDate); + localStorage.setItem('subscriptionToken', data.token); + + // Открываем Setup Wizard + setIsSetupWizardOpen(true); + } else { + showToastNotification('❌ Ошибка создания подписки'); + } + } catch (error) { + console.error('Failed to activate trial:', error); + showToastNotification('❌ Ошибка активации'); + } finally { + setIsLoading(false); + } + }; + return (
} - text="Попробовать 7 дней бесплатно" - onClick={() => (window.location.href = '/plans')} + text={isLoading ? "Активация..." : "Попробовать 7 дней бесплатно"} + onClick={handleActivateTrial} /> )} + {/* Setup Wizard Modal */} + {subscriptionToken && ( + setIsSetupWizardOpen(false)} + subscriptionUrl={getSubscriptionUrl(subscriptionToken)} + username={subscriptionToken} + planType="trial" + expiryDate={expiryDate} + /> + )} + {/* Toast Notification */} {showToast && (
diff --git a/components/SetupWizard.tsx b/components/SetupWizard.tsx new file mode 100644 index 0000000..e3107da --- /dev/null +++ b/components/SetupWizard.tsx @@ -0,0 +1,432 @@ +// Setup Wizard - пошаговая настройка после создания подписки +'use client'; + +import { useState } from 'react'; +import { X, ChevronLeft, Laptop, Smartphone, Check, Copy, QrCode } from 'lucide-react'; + +interface SetupWizardProps { + isOpen: boolean; + onClose: () => void; + subscriptionUrl: string; + username: string; + planType: 'trial' | 'basic' | 'extended' | 'premium'; + expiryDate?: string; +} + +type Step = 'device' | 'app-check' | 'location' | 'final'; +type DeviceType = 'desktop' | 'mobile' | null; +type MobileOS = 'android' | 'ios' | null; + +export default function SetupWizard({ + isOpen, + onClose, + subscriptionUrl, + username, + planType, + expiryDate +}: SetupWizardProps) { + const [step, setStep] = useState('device'); + const [deviceType, setDeviceType] = useState(null); + const [mobileOS, setMobileOS] = useState(null); + const [hasApp, setHasApp] = useState(null); + const [selectedLocations, setSelectedLocations] = useState([]); + const [copied, setCopied] = useState(false); + const [showQR, setShowQR] = useState(false); + + // Локации для выбора (для тарифа "Расширенный") + const locations = [ + { id: 'nl', name: '🇳🇱 Нидерланды', ping: '15ms' }, + { id: 'de', name: '🇩🇪 Германия', ping: '20ms' }, + { id: 'us', name: '🇺🇸 США', ping: '120ms' }, + { id: 'sg', name: '🇸🇬 Сингапур', ping: '180ms' }, + { id: 'jp', name: '🇯🇵 Япония', ping: '160ms' }, + { id: 'uk', name: '🇬🇧 Великобритания', ping: '35ms' }, + ]; + + const needsLocationSelection = planType === 'extended'; + const maxLocations = planType === 'extended' ? 3 : 1; + + const handleCopy = async () => { + await navigator.clipboard.writeText(subscriptionUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const handleDeviceSelect = (type: DeviceType) => { + setDeviceType(type); + if (type === 'desktop') { + setStep('app-check'); + } else { + setStep('app-check'); + } + }; + + const handleAppCheck = (installed: boolean) => { + setHasApp(installed); + if (needsLocationSelection) { + setStep('location'); + } else { + setStep('final'); + } + }; + + const handleLocationToggle = (locationId: string) => { + if (selectedLocations.includes(locationId)) { + setSelectedLocations(selectedLocations.filter(id => id !== locationId)); + } else if (selectedLocations.length < maxLocations) { + setSelectedLocations([...selectedLocations, locationId]); + } + }; + + const canProceedFromLocation = selectedLocations.length === maxLocations; + + const getProgressSteps = () => { + const steps = ['device', 'app-check']; + if (needsLocationSelection) steps.push('location'); + steps.push('final'); + return steps; + }; + + const currentStepIndex = getProgressSteps().indexOf(step); + const totalSteps = getProgressSteps().length; + + if (!isOpen) return null; + + return ( +
+
+ {/* Header */} +
+
+ +

+ {step === 'device' && '🎉 Подписка создана!'} + {step === 'app-check' && deviceType === 'desktop' && '💻 Настройка'} + {step === 'app-check' && deviceType === 'mobile' && '📱 Настройка'} + {step === 'location' && '🌍 Выбор локаций'} + {step === 'final' && '✨ Всё готово!'} +

+
+
+ + {/* Progress bar */} +
+ {Array.from({ length: totalSteps }).map((_, i) => ( +
+ ))} +
+
+ + {/* Content */} +
+ {/* Step 1: Device Selection */} + {step === 'device' && ( +
+

+ {expiryDate ? `Действует до ${expiryDate}` : 'Осталось 2 шага до подключения'} +

+ +

+ Какое у вас устройство? +

+ +
+ + + +
+
+ )} + + {/* Step 2: App Check */} + {step === 'app-check' && deviceType === 'desktop' && ( +
+

+ У вас установлен Umbrix? +

+ +
+ + + +
+ + {hasApp === false && ( +
+

Скачайте Umbrix:

+ + +
+ )} +
+ )} + + {step === 'app-check' && deviceType === 'mobile' && ( +
+

+ Какая у вас система? +

+ +
+ + + +
+ + {mobileOS && ( +
+

У вас есть приложение?

+ +
+ + +
+ + {hasApp === false && ( +
+

Рекомендуем:

+ {mobileOS === 'android' ? ( +
+
• V2RayNG
+
• Hiddify
+
• v2rayTun
+
+ ) : ( +
+
• Shadowrocket (AppStore)
+
• Hiddify (AppStore)
+
+ )} + +
+ )} +
+ )} +
+ )} + + {/* Step 3: Location Selection (только для extended тарифа) */} + {step === 'location' && ( +
+

+ Выберите {maxLocations} локации +

+

+ Выбрано: {selectedLocations.length} из {maxLocations} +

+ +
+ {locations.map((location) => { + const isSelected = selectedLocations.includes(location.id); + const canSelect = selectedLocations.length < maxLocations; + + return ( + + ); + })} +
+ + +
+ )} + + {/* Step 4: Final - Show Link/QR */} + {step === 'final' && ( +
+
+
🎉
+

+ {deviceType === 'desktop' ? 'Скопируйте ссылку и вставьте в Umbrix' : 'Отсканируйте QR код или скопируйте ссылку'} +

+
+ + + + {deviceType === 'mobile' && ( + + )} + + {showQR && ( +
+
QR код
+
{subscriptionUrl}
+
+ )} + +
+ +
+
+ )} +
+
+
+ ); +}