import { useEffect, useState } from "react"; import { motion, AnimatePresence, type Variants } from "framer-motion"; import { Shield, Zap, Copy, Loader2, ArrowRight, Users, Activity, CheckCircle2, Globe, Terminal, Cpu, Database, } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { TonConnectButton, useTonConnectUI, useTonAddress, } from "@tonconnect/ui-react"; import { fetchProfile, activateTrial, initiatePayment, confirmPayment, getPublicConfig, } from "./api"; // --- ТИПИЗАЦИЯ --- interface SubscriptionData { plan_name: string; expires_at: string; status?: string; } interface ReferralData { user_id: string; status: string; total_earned: number; } interface ProfileDataResponse { user?: ProfileDataResponse; // Для поддержки старого мока subscription?: SubscriptionData; referrals?: ReferralData[]; recruits?: number; earned?: number; // Плоские свойства с бэкенда id?: string; tg_id?: string | number; username?: string; tg_username?: string; role?: string; balance?: number; game_tickets?: number; has_used_trial?: boolean; } interface UIPlan { name: string; price: number; price_usd: number; price_rub: number; } // Вспомогательная функция снаружи компонента, чтобы обойти правило react-hooks/purity const getTonTxValidUntil = () => Math.floor(Date.now() / 1000) + 360; // --- СЛОВАРИ И КОНСТАНТЫ --- const DICT = { en: { auth_ready: "SYSTEM ONLINE", operative: "Operative ID", balance: "Credits", tickets: "Access Tokens", uplink_status: "Node Status", expires: "Valid Until", offline: "DISCONNECTED", no_active_sub: "No active grid connection.", run_breach: "Execute Breach Protocol", system_override: "Sys.Override()", manage_plan: "Manage Uplink", acquire: "Acquire", activate_trial: "Initialize Trial", trial_used: "Trial Exhausted", syndicate: "Syndicate Grid", recruits: "Nodes Hacked", earned: "Eddies Extracted", ref_desc: "Expand the network. Extract 100 E$ per initialized node.", copied: "Data copied to clipboard.", plans: { TRIAL: { tag: "Trial Run", desc: "Experience the Grid. 3 days of unrestricted access.", features: ["5GB Bandwidth", "No logs", "Standard Nodes"], }, GHOST: { tag: "Stealth Node", desc: "The ideal choice for personal use. Minimal digital footprint.", features: ["1 device", "ChaCha20-Poly1305", "DPI Protection"], }, NETRUNNER: { tag: "Pro Interface", desc: "For professionals working with multiple rigs simultaneously.", features: ["5 devices", "Priority Support", "FakeDNS Module"], }, MAINFRAME: { tag: "Command Center", desc: "The ultimate solution for teams and maximum network independence.", features: [ "Unlimited devices", "High-Load Ready", "24/7 Priority Support", ], }, }, }, ru: { auth_ready: "СИСТЕМА АКТИВНА", operative: "ID Оперативника", balance: "Кредиты", tickets: "Токены Доступа", uplink_status: "Статус Узла", expires: "Действует до", offline: "ОТКЛЮЧЕНО", no_active_sub: "Нет активного подключения к Сети.", run_breach: "Протокол Взлома", system_override: "Sys.Override()", manage_plan: "Управление Узлом", acquire: "Интегрировать", activate_trial: "Запуск Триала", trial_used: "Триал Использован", syndicate: "Синдикат", recruits: "Взломано узлов", earned: "Извлечено (E$)", ref_desc: "Расширяй сеть. Извлекай 100 E$ за каждый новый узел.", copied: "Данные скопированы.", plans: { TRIAL: { tag: "Тестовый Прогон", desc: "Испытай Сеть. 3 дня безлимитного доступа.", features: ["5GB Трафика", "Без логов", "Стандартные узлы"], }, GHOST: { tag: "Стелс-Узел", desc: "Идеально для себя. Минимальный цифровой след.", features: ["1 устройство", "ChaCha20-Poly1305", "Защита от DPI"], }, NETRUNNER: { tag: "Про-Интерфейс", desc: "Для работы со смартфоном, декой и планшетом одновременно.", features: ["5 устройств", "Приоритетная база", "Модуль FakeDNS"], }, MAINFRAME: { tag: "Командный Центр", desc: "Ультимативное решение для команд и полной независимости.", features: ["Безлимит устройств", "High-Load сервера", "Поддержка 24/7"], }, }, }, }; const UI_PLANS: UIPlan[] = [ { name: "TRIAL", price: 0, price_usd: 0, price_rub: 0 }, { name: "GHOST", price: 5, price_usd: 5, price_rub: 500 }, { name: "NETRUNNER", price: 9, price_usd: 9, price_rub: 900 }, { name: "MAINFRAME", price: 20, price_usd: 20, price_rub: 2000 }, ]; const getLang = (): "en" | "ru" => { const l = localStorage.getItem("lang"); return l === "ru" ? "ru" : "en"; }; // Анимационные варианты для элементов HUD const containerVariants: Variants = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.1 }, }, }; const itemVariants: Variants = { hidden: { opacity: 0, y: 10 }, show: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 100 } }, }; export default function Profile() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const [showPlans, setShowPlans] = useState(false); const navigate = useNavigate(); const [lang, setLang] = useState<"en" | "ru">(getLang); const t = DICT[lang]; const [botUsername, setBotUsername] = useState("ntrnr_vpn_bot"); // TON Connect Hooks const [tonConnectUI] = useTonConnectUI(); const userFriendlyAddress = useTonAddress(); useEffect(() => { getPublicConfig().then((cfg) => setBotUsername(cfg.bot_username)); }, []); useEffect(() => { fetchProfile() .then((res: ProfileDataResponse) => setData(res)) .catch((err) => { const e = err as Error; console.error(e); setError(e.message || "Failed to connect to Netrunner Core"); }) .finally(() => setLoading(false)); }, []); const toggleLang = () => { const next = lang === "en" ? "ru" : "en"; setLang(next); localStorage.setItem("lang", next); }; if (loading) { return (

Establishing secure uplink...

); } if (error || !data) { return (

[ CONNECTION REFUSED ]

Handshake failed. Authenticate via secure terminal.

); } const user = data.user || data; const subscription = data.subscription; const hasSub = subscription && subscription.plan_name; const recruits = data.referrals ? data.referrals.length : data.recruits || 0; const earned = data.earned || 0; const copyRef = () => { navigator.clipboard.writeText( `https://t.me/${botUsername}?start=${user.tg_id || user.id}`, ); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const getPrice = (usd: number, rub: number) => { if (usd === 0) return "FREE"; return lang === "ru" ? `${rub} ₽/мес` : `$${usd}/mo`; }; const handleTrial = async () => { try { await activateTrial(); const newData = await fetchProfile(); setData(newData); } catch (err) { const e = err as Error; alert(`[SYS.ERR] ${e.message}`); } }; const executeTonPayment = async (planName: string) => { if (!userFriendlyAddress) { tonConnectUI.openModal(); return; } try { const targetCurrency = lang === "ru" ? "RUB" : "USD"; // Бэкенд возвращает destination, amount и comment const { checkout_data } = await initiatePayment( planName, "TON", targetCurrency, ); const transaction = { validUntil: getTonTxValidUntil(), messages: [ { address: checkout_data.destination, amount: checkout_data.amount.toString(), // Кошелек сам упакует комментарий, если его передать в payload как text: payload: `text:${checkout_data.comment}`, }, ], }; const result = await tonConnectUI.sendTransaction(transaction); // Отправляем результат (boc) на бэкенд для верификации await confirmPayment(checkout_data.comment, result.boc); const newData = await fetchProfile(); setData(newData); } catch (err) { console.error(err); alert(`[SYS.ERR] TRANSACTION ABORTED`); } }; return (
[{t.auth_ready}]

Netrunner_OS

{t.operative}

@{user.username || user.tg_username || user.tg_id || "Anon"}

{t.balance}

{user.balance || 0}{" "} E$

{t.uplink_status}
{hasSub ? (
[{subscription.plan_name}]

{t.expires}:{" "} {new Date(subscription.expires_at).toLocaleDateString()}

) : ( [{t.offline}] )}
{t.tickets}:{" "} {user.game_tickets || 0} {!hasSub && ( {t.no_active_sub} )}
navigate("/hack")} className="w-full relative bg-primary/10 hover:bg-primary/20 border border-primary/40 text-primary font-bold tracking-widest uppercase p-4 rounded-xl transition-all flex justify-center items-center gap-3 shadow-[0_0_15px_rgba(139,61,255,0.1)] hover:shadow-[0_0_25px_rgba(139,61,255,0.3)] group overflow-hidden" >
[ {t.run_breach} ]

_ {t.system_override}

{showPlans && (
{UI_PLANS.map((plan: UIPlan) => { const planData = t.plans[ plan.name as keyof typeof t.plans ] || { tag: "", desc: "", features: [] }; const isTrial = plan.name === "TRIAL"; const isTrialUsed = isTrial && user.has_used_trial; return (

{plan.name}

{planData.tag}

{getPrice( plan.price_usd || plan.price, plan.price_rub || plan.price * 100, )}

{planData.desc}

    {planData.features.map((f: string, i: number) => (
  • {">"}{" "} {f}
  • ))}
); })}
)}

_ {t.syndicate}

{recruits}

{t.recruits}

{earned}

{t.earned}

{t.ref_desc}

); }