Files
app_umbrix/app/dashboard/page.tsx

305 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// app/dashboard/page.tsx - Main dashboard for existing users
// Shows subscription status, QR code, referral stats, quick actions
'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import QRCodeModal from '@/components/QRCodeModal';
import ReferralModal from '@/components/ReferralModal';
import { marzbanApi } from '@/lib/marzban-api';
import { getSubscriptionUrl } from '@/lib/constants';
import {
Shield,
Settings,
Gift,
HelpCircle,
QrCode,
Copy,
Share2,
CheckCircle,
AlertCircle,
Clock,
Users,
} from 'lucide-react';
export default function Dashboard() {
const router = useRouter();
const [isLoading, setIsLoading] = useState(true);
const [subscriptionToken, setSubscriptionToken] = useState<string | null>(null);
const [subscriptionStatus, setSubscriptionStatus] = useState<'active' | 'expired' | 'trial'>('active');
const [expiryDate, setExpiryDate] = useState<string>('');
const [daysRemaining, setDaysRemaining] = useState<number>(0);
const [username, setUsername] = useState<string>('');
const [isQROpen, setIsQROpen] = useState(false);
const [isReferralOpen, setIsReferralOpen] = useState(false);
const [showToast, setShowToast] = useState(false);
const [toastMessage, setToastMessage] = useState('');
const [referralCount, setReferralCount] = useState(0);
const [bonusDays, setBonusDays] = useState(0);
const subscriptionUrl = subscriptionToken ? getSubscriptionUrl(subscriptionToken) : '';
useEffect(() => {
loadUserData();
}, []);
const loadUserData = async () => {
try {
// Get Telegram data
const telegramWebApp = (window as any).Telegram?.WebApp;
const telegramId = telegramWebApp?.initDataUnsafe?.user?.id;
const telegramUsername = telegramWebApp?.initDataUnsafe?.user?.username;
if (!telegramId && !telegramUsername) {
console.log('❌ No Telegram data - redirecting to home');
router.push('/');
return;
}
// Load subscription
const params = new URLSearchParams();
if (telegramId) params.append('telegramId', telegramId.toString());
if (telegramUsername) params.append('telegramUsername', telegramUsername);
const response = await fetch(`/api/user-subscription?${params.toString()}`);
const data = await response.json();
if (!data.success || !data.hasSubscription) {
console.log('❌ No subscription - redirecting to home');
router.push('/');
return;
}
setSubscriptionToken(data.token);
setUsername(data.username);
setSubscriptionStatus(data.status === 'active' ? 'active' : 'expired');
if (data.expire) {
setExpiryDate(marzbanApi.formatExpireDate(data.expire));
// Calculate days remaining
const expireTimestamp = data.expire * 1000;
const now = Date.now();
const diff = expireTimestamp - now;
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
setDaysRemaining(days > 0 ? days : 0);
}
// Load referral stats
const referralResponse = await fetch(`/api/referral/stats?username=${data.username}`);
const referralData = await referralResponse.json();
if (referralData.success) {
setReferralCount(referralData.referral_count || 0);
setBonusDays(referralData.bonus_days_earned || 0);
}
} catch (error) {
console.error('Failed to load user data:', error);
router.push('/');
} finally {
setIsLoading(false);
}
};
const showToastNotification = (message: string) => {
setToastMessage(message);
setShowToast(true);
setTimeout(() => setShowToast(false), 3000);
};
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
showToastNotification('✅ Скопировано в буфер обмена!');
} catch (err) {
console.error('Failed to copy:', err);
showToastNotification('❌ Ошибка копирования');
}
};
const shareReferralLink = async () => {
const botUsername = process.env.NEXT_PUBLIC_TELEGRAM_BOT_USERNAME || 'Chat_8n8_bot';
const referralUrl = `https://t.me/${botUsername}?start=ref_${username}`;
const telegramWebApp = (window as any).Telegram?.WebApp;
if (telegramWebApp?.openTelegramLink) {
const shareText = encodeURIComponent(`🚀 Попробуй Umbrix VPN! Получи 7 дней бесплатно по моей ссылке:\n${referralUrl}`);
telegramWebApp.openTelegramLink(`https://t.me/share/url?url=${referralUrl}&text=${shareText}`);
} else {
copyToClipboard(referralUrl);
}
};
if (isLoading) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 text-white flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-16 w-16 border-b-4 border-blue-500 mx-auto mb-6"></div>
<p className="text-slate-400">Загрузка...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 text-white">
{/* Header */}
<header className="border-b border-slate-700 bg-slate-900/50 backdrop-blur-sm sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
<h1 className="text-xl font-bold">🚀 Umbrix VPN</h1>
<Link href="/help">
<button className="p-2 hover:bg-slate-700 rounded-lg transition-colors">
<HelpCircle className="h-6 w-6" />
</button>
</Link>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 py-8 space-y-6">
{/* Subscription Status Card */}
<div className="bg-slate-800/50 border border-slate-700 rounded-lg p-6">
<div className="flex items-start justify-between mb-4">
<div>
<h2 className="text-2xl font-bold mb-2">Ваша подписка</h2>
<p className="text-slate-400">@{username}</p>
</div>
{subscriptionStatus === 'active' ? (
<div className="flex items-center gap-2 px-3 py-1 bg-green-600/20 border border-green-500 rounded-full">
<CheckCircle className="h-4 w-4 text-green-400" />
<span className="text-sm font-medium">Активна</span>
</div>
) : (
<div className="flex items-center gap-2 px-3 py-1 bg-red-600/20 border border-red-500 rounded-full">
<AlertCircle className="h-4 w-4 text-red-400" />
<span className="text-sm font-medium">Истекла</span>
</div>
)}
</div>
<div className="grid grid-cols-2 gap-4">
<div className="bg-slate-900/50 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Clock className="h-5 w-5 text-blue-400" />
<span className="text-sm text-slate-400">Осталось дней</span>
</div>
<p className="text-3xl font-bold">{daysRemaining}</p>
<p className="text-sm text-slate-400 mt-1">{expiryDate}</p>
</div>
<div className="bg-slate-900/50 rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Users className="h-5 w-5 text-purple-400" />
<span className="text-sm text-slate-400">Рефералы</span>
</div>
<p className="text-3xl font-bold">{referralCount}</p>
<p className="text-sm text-slate-400 mt-1">+{bonusDays} дней заработано</p>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => setIsQROpen(true)}
className="bg-slate-800/50 hover:bg-slate-700 border border-slate-700 rounded-lg p-6 flex flex-col items-center gap-3 transition-colors"
>
<QrCode className="h-8 w-8 text-blue-500" />
<span className="font-semibold">QR Код</span>
</button>
<button
onClick={() => copyToClipboard(subscriptionUrl)}
className="bg-slate-800/50 hover:bg-slate-700 border border-slate-700 rounded-lg p-6 flex flex-col items-center gap-3 transition-colors"
>
<Copy className="h-8 w-8 text-green-500" />
<span className="font-semibold">Копировать ссылку</span>
</button>
<button
onClick={shareReferralLink}
className="bg-slate-800/50 hover:bg-slate-700 border border-slate-700 rounded-lg p-6 flex flex-col items-center gap-3 transition-colors"
>
<Share2 className="h-8 w-8 text-purple-500" />
<span className="font-semibold">Пригласить друга</span>
</button>
<Link href="/referral">
<button className="w-full bg-slate-800/50 hover:bg-slate-700 border border-slate-700 rounded-lg p-6 flex flex-col items-center gap-3 transition-colors">
<Gift className="h-8 w-8 text-yellow-500" />
<span className="font-semibold">Реферальная программа</span>
</button>
</Link>
</div>
{/* Referral Progress (if has referrals) */}
{referralCount > 0 && (
<div className="bg-gradient-to-r from-purple-600/20 to-blue-600/20 border border-purple-500/50 rounded-lg p-6">
<h3 className="text-lg font-bold mb-4">🎉 Прогресс реферальной программы</h3>
<div className="space-y-3">
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-slate-300">До следующего бонуса</span>
<span className="text-sm font-bold">{referralCount}/5</span>
</div>
<div className="h-3 bg-slate-900/50 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-purple-500 to-blue-500 transition-all"
style={{ width: `${(referralCount / 5) * 100}%` }}
/>
</div>
</div>
<p className="text-sm text-slate-300">
{referralCount >= 5
? '🎁 Вы получили месяц в подарок!'
: `Пригласите еще ${5 - referralCount} друзей и получите месяц бесплатно!`}
</p>
</div>
</div>
)}
{/* Setup Guide Link */}
<Link href="/setup">
<div className="bg-slate-800/50 border border-slate-700 rounded-lg p-6 flex items-center justify-between hover:bg-slate-700 transition-colors">
<div className="flex items-center gap-4">
<Shield className="h-8 w-8 text-blue-500" />
<div>
<h3 className="font-bold">Инструкция по настройке</h3>
<p className="text-sm text-slate-400">Как подключить VPN на вашем устройстве</p>
</div>
</div>
<Settings className="h-6 w-6 text-slate-400" />
</div>
</Link>
{/* Spacer для BottomNav */}
<div className="h-20" />
</main>
{/* Modals */}
{isQROpen && subscriptionUrl && (
<QRCodeModal isOpen={isQROpen} url={subscriptionUrl} onClose={() => setIsQROpen(false)} />
)}
{isReferralOpen && (
<ReferralModal
isOpen={isReferralOpen}
onClose={() => setIsReferralOpen(false)}
referralUrl={`https://t.me/${process.env.NEXT_PUBLIC_TELEGRAM_BOT_USERNAME || 'Chat_8n8_bot'}?start=ref_${username}`}
onShare={shareReferralLink}
onCopy={() => copyToClipboard(`https://t.me/${process.env.NEXT_PUBLIC_TELEGRAM_BOT_USERNAME || 'Chat_8n8_bot'}?start=ref_${username}`)}
/>
)}
{/* Toast */}
{showToast && (
<div className="fixed bottom-24 left-1/2 -translate-x-1/2 bg-green-600 text-white px-6 py-3 rounded-lg shadow-lg z-50 animate-fade-in-up">
{toastMessage}
</div>
)}
</div>
);
}