301 lines
11 KiB
TypeScript
301 lines
11 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { useRouter } from 'next/navigation';
|
||
import Link from 'next/link';
|
||
import {
|
||
Shield,
|
||
ArrowLeft,
|
||
Users,
|
||
Gift,
|
||
TrendingUp,
|
||
Calendar,
|
||
CheckCircle,
|
||
XCircle,
|
||
Clock,
|
||
Copy,
|
||
Share2
|
||
} from 'lucide-react';
|
||
|
||
interface ReferralStats {
|
||
referral_count: number;
|
||
bonus_days_earned: number;
|
||
total_referrals_used: number;
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
|
||
interface ReferredUser {
|
||
username: string;
|
||
created_at: string;
|
||
status: string;
|
||
}
|
||
|
||
export default function ReferralPage() {
|
||
const router = useRouter();
|
||
const [stats, setStats] = useState<ReferralStats | null>(null);
|
||
const [referredUsers, setReferredUsers] = useState<ReferredUser[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [username, setUsername] = useState<string>('');
|
||
const [referralUrl, setReferralUrl] = useState<string>('');
|
||
const [showToast, setShowToast] = useState(false);
|
||
const [toastMessage, setToastMessage] = useState('');
|
||
|
||
useEffect(() => {
|
||
const loadReferralStats = async () => {
|
||
// Get username from localStorage
|
||
const token = localStorage.getItem('subscriptionToken');
|
||
if (!token) {
|
||
router.push('/');
|
||
return;
|
||
}
|
||
|
||
// subscriptionToken = Marzban username (полное имя, НЕ split по _)
|
||
const userId = token;
|
||
setUsername(userId);
|
||
|
||
// Generate referral URL
|
||
const botUsername = process.env.NEXT_PUBLIC_TELEGRAM_BOT_USERNAME || 'Dorod_vps_bot';
|
||
const url = `https://t.me/${botUsername}?start=ref_${userId}`;
|
||
setReferralUrl(url);
|
||
|
||
// Fetch stats from API
|
||
try {
|
||
const response = await fetch(`/api/referral/stats?username=${userId}`);
|
||
const data = await response.json();
|
||
|
||
if (data.success && data.hasReferrals) {
|
||
setStats(data.stats);
|
||
setReferredUsers(data.referred_users || []);
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to load referral stats:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
loadReferralStats();
|
||
}, [router]);
|
||
|
||
const copyToClipboard = async (text: string) => {
|
||
try {
|
||
await navigator.clipboard.writeText(text);
|
||
showToastNotification('✅ Скопировано в буфер обмена!');
|
||
} catch (err) {
|
||
console.error('Failed to copy:', err);
|
||
showToastNotification('❌ Ошибка копирования');
|
||
}
|
||
};
|
||
|
||
const showToastNotification = (message: string) => {
|
||
setToastMessage(message);
|
||
setShowToast(true);
|
||
setTimeout(() => setShowToast(false), 3000);
|
||
};
|
||
|
||
const shareReferralLink = async () => {
|
||
const shareText = `🚀 Попробуй Umbrix VPN - быстрый и безопасный VPN!\n\n✨ Получи 7 дней бесплатно по моей ссылке:\n${referralUrl}`;
|
||
|
||
if (navigator.share) {
|
||
try {
|
||
await navigator.share({
|
||
title: 'Umbrix VPN - Пригласи друга!',
|
||
text: shareText,
|
||
url: referralUrl,
|
||
});
|
||
} catch (err) {
|
||
if ((err as Error).name !== 'AbortError') {
|
||
console.error('Share failed:', err);
|
||
copyToClipboard(shareText);
|
||
}
|
||
}
|
||
} else {
|
||
copyToClipboard(shareText);
|
||
}
|
||
};
|
||
|
||
const getStatusIcon = (status: string) => {
|
||
switch (status) {
|
||
case 'active':
|
||
return <CheckCircle className="w-4 h-4 text-green-500" />;
|
||
case 'expired':
|
||
return <Clock className="w-4 h-4 text-yellow-500" />;
|
||
case 'disabled':
|
||
return <XCircle className="w-4 h-4 text-red-500" />;
|
||
default:
|
||
return <Clock className="w-4 h-4 text-slate-400" />;
|
||
}
|
||
};
|
||
|
||
const getStatusText = (status: string) => {
|
||
switch (status) {
|
||
case 'active':
|
||
return 'Активен';
|
||
case 'expired':
|
||
return 'Истёк';
|
||
case 'disabled':
|
||
return 'Отключён';
|
||
default:
|
||
return status;
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center" style={{ background: 'var(--bg-app)' }}>
|
||
<div className="text-center">
|
||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 mx-auto" style={{ borderColor: 'var(--primary)' }}></div>
|
||
<p className="mt-4 text-slate-400">Загрузка...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen" style={{ background: 'var(--bg-app)', color: 'var(--text-white)' }}>
|
||
{/* Header */}
|
||
<header className="flex items-center justify-between p-4 border-b sticky top-0 z-40 backdrop-blur-sm" style={{ borderColor: 'var(--border)', background: 'var(--bg-card)' }}>
|
||
<button onClick={() => router.back()} className="p-2 rounded-lg hover:opacity-70 transition-opacity" style={{ background: 'var(--bg-elevated)' }}>
|
||
<ArrowLeft className="w-5 h-5" style={{ color: 'var(--text-primary)' }} />
|
||
</button>
|
||
<div className="flex items-center gap-2">
|
||
<Shield className="w-6 h-6" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-xl font-bold">Реферальная программа</span>
|
||
</div>
|
||
<div className="w-9" /> {/* Spacer */}
|
||
</header>
|
||
|
||
{/* Main Content */}
|
||
<main className="px-6 py-8 max-w-2xl mx-auto">
|
||
{/* Stats Cards */}
|
||
<div className="grid grid-cols-3 gap-3 mb-6">
|
||
<div className="rounded-xl p-4 text-center" style={{ background: 'var(--bg-card)' }}>
|
||
<Users className="w-6 h-6 mx-auto mb-2" style={{ color: 'var(--primary)' }} />
|
||
<div className="text-2xl font-bold mb-1">{stats?.referral_count || 0}</div>
|
||
<div className="text-xs text-slate-400">Рефералов</div>
|
||
</div>
|
||
|
||
<div className="rounded-xl p-4 text-center" style={{ background: 'var(--bg-card)' }}>
|
||
<Gift className="w-6 h-6 mx-auto mb-2" style={{ color: 'var(--primary)' }} />
|
||
<div className="text-2xl font-bold mb-1">{stats?.bonus_days_earned || 0}</div>
|
||
<div className="text-xs text-slate-400">Дней бонуса</div>
|
||
</div>
|
||
|
||
<div className="rounded-xl p-4 text-center" style={{ background: 'var(--bg-card)' }}>
|
||
<TrendingUp className="w-6 h-6 mx-auto mb-2" style={{ color: 'var(--primary)' }} />
|
||
<div className="text-2xl font-bold mb-1">+{(stats?.referral_count || 0) >= 5 ? 30 : 0}</div>
|
||
<div className="text-xs text-slate-400">Milestone</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Referral Link */}
|
||
<div className="rounded-xl p-4 mb-6" style={{ background: 'var(--bg-card)' }}>
|
||
<h3 className="text-sm font-semibold mb-3 flex items-center gap-2">
|
||
<Gift className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
Ваша реферальная ссылка
|
||
</h3>
|
||
|
||
<div className="flex gap-2 mb-3">
|
||
<input
|
||
type="text"
|
||
value={referralUrl}
|
||
readOnly
|
||
className="flex-1 px-3 py-2 rounded-lg text-sm"
|
||
style={{ background: 'var(--bg-elevated)', color: 'var(--text-white)', border: '1px solid var(--border)' }}
|
||
/>
|
||
<button
|
||
onClick={() => copyToClipboard(referralUrl)}
|
||
className="p-2 rounded-lg hover:opacity-80 transition-opacity"
|
||
style={{ background: 'var(--bg-elevated)' }}
|
||
>
|
||
<Copy className="w-5 h-5" style={{ color: 'var(--primary)' }} />
|
||
</button>
|
||
</div>
|
||
|
||
<button
|
||
onClick={shareReferralLink}
|
||
className="w-full flex items-center justify-center gap-2 py-3 rounded-lg font-medium hover:opacity-80 transition-opacity"
|
||
style={{ background: 'var(--primary)', color: 'white' }}
|
||
>
|
||
<Share2 className="w-5 h-5" />
|
||
Поделиться ссылкой
|
||
</button>
|
||
</div>
|
||
|
||
{/* Bonus Info */}
|
||
<div className="rounded-xl p-4 mb-6" style={{ background: 'var(--bg-card)', border: '1px solid var(--border)' }}>
|
||
<h3 className="text-sm font-semibold mb-3">💰 Как работают бонусы</h3>
|
||
<ul className="space-y-2 text-sm text-slate-300">
|
||
<li className="flex items-start gap-2">
|
||
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0" style={{ color: 'var(--primary)' }} />
|
||
<span>+7 дней за каждого приглашённого друга</span>
|
||
</li>
|
||
<li className="flex items-start gap-2">
|
||
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0" style={{ color: 'var(--primary)' }} />
|
||
<span>+30 дней бонус за каждые 5 рефералов</span>
|
||
</li>
|
||
<li className="flex items-start gap-2">
|
||
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0" style={{ color: 'var(--primary)' }} />
|
||
<span>Бонусы начисляются автоматически</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
{/* Referred Users */}
|
||
{referredUsers.length > 0 && (
|
||
<div className="rounded-xl p-4" style={{ background: 'var(--bg-card)' }}>
|
||
<h3 className="text-sm font-semibold mb-3 flex items-center gap-2">
|
||
<Users className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
Ваши рефералы ({referredUsers.length})
|
||
</h3>
|
||
|
||
<div className="space-y-2">
|
||
{referredUsers.map((user, index) => (
|
||
<div
|
||
key={index}
|
||
className="flex items-center justify-between p-3 rounded-lg"
|
||
style={{ background: 'var(--bg-elevated)' }}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
{getStatusIcon(user.status)}
|
||
<div>
|
||
<div className="text-sm font-medium">{user.username}</div>
|
||
<div className="text-xs text-slate-400">
|
||
{new Date(user.created_at).toLocaleDateString('ru-RU')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="text-xs px-2 py-1 rounded" style={{ background: 'var(--bg-card)' }}>
|
||
{getStatusText(user.status)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{referredUsers.length === 0 && (
|
||
<div className="text-center py-12 rounded-xl" style={{ background: 'var(--bg-card)' }}>
|
||
<Users className="w-16 h-16 mx-auto mb-4 opacity-50" style={{ color: 'var(--primary)' }} />
|
||
<p className="text-slate-400 mb-2">Пока нет рефералов</p>
|
||
<p className="text-sm text-slate-500">Поделитесь ссылкой с друзьями!</p>
|
||
</div>
|
||
)}
|
||
</main>
|
||
|
||
{/* Spacer для нижней навигации */}
|
||
<div className="h-20" />
|
||
|
||
{/* Toast Notification */}
|
||
{showToast && (
|
||
<div className="fixed bottom-24 left-1/2 transform -translate-x-1/2 z-50 animate-fade-in">
|
||
<div className="px-6 py-3 rounded-full shadow-lg border flex items-center gap-2" style={{ background: 'var(--bg-card)', borderColor: 'var(--border)' }}>
|
||
<span>{toastMessage}</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|