Files
app_umbrix/app/referral/page.tsx

301 lines
11 KiB
TypeScript
Raw 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.
'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>
);
}