- Выбор устройства (💻 Компьютер / 📱 Телефон) - Проверка наличия приложения - Выбор локаций для Extended тарифа (3 из списка) - Показ ссылки/QR кода - Автоматическое открытие после активации Trial - Прогресс бар для отслеживания шагов
650 lines
23 KiB
TypeScript
650 lines
23 KiB
TypeScript
// Главная страница приложения (Home Page)
|
||
// Показывает статус подписки пользователя и кнопки действий
|
||
// URL: https://app.umbrix.net/
|
||
|
||
'use client'; // Next.js 13: это клиентский компонент (с useState, useEffect)
|
||
|
||
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 SetupWizard from '@/components/SetupWizard';
|
||
import { marzbanApi } from '@/lib/marzban-api';
|
||
import { getSubscriptionUrl, MARZBAN_SUBSCRIPTION_URL } from '@/lib/constants';
|
||
import {
|
||
Shield,
|
||
Settings,
|
||
Gift,
|
||
DollarSign,
|
||
Wrench,
|
||
User,
|
||
HelpCircle,
|
||
ExternalLink,
|
||
X,
|
||
Key,
|
||
CreditCard,
|
||
Lock,
|
||
Eye,
|
||
Ban,
|
||
MessageCircle,
|
||
UserPlus,
|
||
QrCode,
|
||
Copy,
|
||
ChevronRight,
|
||
Share2,
|
||
} from 'lucide-react';
|
||
|
||
export default function Home() {
|
||
const router = useRouter();
|
||
const [hasSubscription, setHasSubscription] = useState(false);
|
||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||
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<string>('');
|
||
const [showToast, setShowToast] = useState(false);
|
||
const [toastMessage, setToastMessage] = useState('');
|
||
const [subscriptionToken, setSubscriptionToken] = useState<string | null>(null);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
|
||
const subscriptionUrl = subscriptionToken ? getSubscriptionUrl(subscriptionToken) : MARZBAN_SUBSCRIPTION_URL;
|
||
|
||
const showToastNotification = (message: string) => {
|
||
setToastMessage(message);
|
||
setShowToast(true);
|
||
setTimeout(() => setShowToast(false), 3000);
|
||
};
|
||
|
||
// Загружаем подписку при монтировании - ЧЕРЕЗ API, НЕ localStorage!
|
||
useEffect(() => {
|
||
const loadSubscription = async () => {
|
||
// Получаем Telegram данные
|
||
const telegramWebApp = (window as any).Telegram?.WebApp;
|
||
const telegramId = telegramWebApp?.initDataUnsafe?.user?.id;
|
||
const telegramUsername = telegramWebApp?.initDataUnsafe?.user?.username;
|
||
|
||
console.log('🔍 Loading subscription for:', { telegramId, telegramUsername });
|
||
|
||
if (!telegramId && !telegramUsername) {
|
||
console.log('⚠️ No Telegram data - probably opened in browser');
|
||
setIsLoading(false);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Запрашиваем подписку по Telegram ID через API
|
||
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();
|
||
|
||
console.log('📥 API response:', data);
|
||
|
||
if (data.success && data.hasSubscription) {
|
||
setSubscriptionToken(data.token);
|
||
setHasSubscription(true);
|
||
const status = data.status === 'active' ? 'active' : 'expired';
|
||
setSubscriptionStatus(status);
|
||
if (data.expire) {
|
||
setExpiryDate(marzbanApi.formatExpireDate(data.expire));
|
||
}
|
||
} else {
|
||
console.log('❌ No subscription found');
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to load subscription:', error);
|
||
}
|
||
|
||
setIsLoading(false);
|
||
};
|
||
|
||
loadSubscription();
|
||
}, []); // Загружаем при каждом монтировании компонента
|
||
// ОТКЛЮЧАЕМ автоматическую проверку API при загрузке (чтобы не зависала страница)
|
||
// API проверка будет только на странице /subscription/[token]
|
||
// useEffect(() => {
|
||
// async function checkSubscription() {
|
||
// try {
|
||
// const userData = await marzbanApi.getUserInfo(subscriptionToken);
|
||
//
|
||
// if (userData.status === 'active') {
|
||
// setHasSubscription(true);
|
||
// setSubscriptionStatus('active');
|
||
// setExpiryDate(marzbanApi.formatExpireDate(userData.expire));
|
||
// } else if (userData.status === 'expired') {
|
||
// setHasSubscription(false);
|
||
// setSubscriptionStatus('expired');
|
||
// setExpiryDate(marzbanApi.formatExpireDate(userData.expire));
|
||
// } else {
|
||
// setHasSubscription(false);
|
||
// setSubscriptionStatus('none');
|
||
// }
|
||
// } catch (error) {
|
||
// console.error('Failed to check subscription:', error);
|
||
// setHasSubscription(false);
|
||
// setSubscriptionStatus('none');
|
||
// }
|
||
// }
|
||
//
|
||
// checkSubscription();
|
||
// }, []);
|
||
|
||
const copyToClipboard = async (text: string) => {
|
||
try {
|
||
await navigator.clipboard.writeText(text);
|
||
showToastNotification('✅ Скопировано в буфер обмена!');
|
||
} catch (err) {
|
||
console.error('Failed to copy:', err);
|
||
showToastNotification('❌ Ошибка копирования');
|
||
}
|
||
};
|
||
const shareReferralLink = async () => {
|
||
// Генерируем реферальную ссылку (TODO: заменить на реальный user ID после авторизации)
|
||
const userId = subscriptionToken?.split('_')[0] || 'DEMO';
|
||
const referralUrl = `https://t.me/umbrix_bot?start=ref_${userId}`;
|
||
const shareText = `🚀 Попробуй Umbrix VPN - быстрый и безопасный VPN!\n\n✨ Получи 7 дней бесплатно по моей ссылке:\n${referralUrl}`;
|
||
|
||
// Проверяем поддержку Web Share API
|
||
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);
|
||
// Fallback: копируем в буфер
|
||
copyToClipboard(shareText);
|
||
}
|
||
}
|
||
} else {
|
||
// Fallback для браузеров без поддержки Share API
|
||
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 (
|
||
<div
|
||
className="min-h-screen flex flex-col"
|
||
style={{ background: 'var(--bg-app)' }}
|
||
>
|
||
{/* 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)' }}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<Shield className="w-6 h-6" style={{ color: 'var(--primary)' }} />
|
||
<span
|
||
className="text-xl font-bold"
|
||
style={{ color: 'var(--text-white)' }}
|
||
>
|
||
Umbrix
|
||
</span>
|
||
</div>
|
||
<button
|
||
className="p-2 rounded-lg"
|
||
style={{ background: 'var(--bg-card)' }}
|
||
>
|
||
<Settings
|
||
className="w-5 h-5"
|
||
style={{ color: 'var(--text-primary)' }}
|
||
/>
|
||
</button>
|
||
</header>
|
||
|
||
{/* Бегущая строка с преимуществами */}
|
||
<div
|
||
className="w-full overflow-hidden border-b py-2"
|
||
style={{ borderColor: 'var(--border)', background: 'var(--bg-card)' }}
|
||
>
|
||
<div className="animate-marquee whitespace-nowrap flex items-center gap-8 text-sm">
|
||
<div className="flex items-center gap-2">
|
||
<Shield className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Без логов</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Lock className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Без регистрации</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Eye className="w-4 h-4 line-through opacity-50" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Не отслеживаем</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Ban className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Не продаём ваш трафик</span>
|
||
</div>
|
||
{/* Дублируем для бесшовной анимации */}
|
||
<div className="flex items-center gap-2">
|
||
<Shield className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Без логов</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Lock className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Без регистрации</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Eye className="w-4 h-4 line-through opacity-50" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Не отслеживаем</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Ban className="w-4 h-4" style={{ color: 'var(--primary)' }} />
|
||
<span className="text-slate-300">Не продаём ваш трафик</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Main Content */}
|
||
<main className="flex-1 flex flex-col items-center justify-center px-6 py-8">
|
||
{/* Hero Section - Логотип и статус */}
|
||
<div className="text-center mb-8">
|
||
<div className="text-6xl mb-4">🛡️</div>
|
||
<h1 className="text-2xl font-bold mb-2">Umbrix VPN</h1>
|
||
<p className="text-sm opacity-70">Быстрый и безопасный VPN</p>
|
||
</div>
|
||
|
||
{/* Status */}
|
||
<div className="text-center mb-8">
|
||
{hasSubscription ? (
|
||
<Link
|
||
href={`/subscription/${subscriptionToken}`}
|
||
className="text-sm opacity-70 hover:opacity-100 transition-opacity cursor-pointer inline-block"
|
||
>
|
||
Моя подписка ✅ Активна до {expiryDate || '...'}
|
||
</Link>
|
||
) : subscriptionStatus === 'expired' ? (
|
||
<div className="text-sm opacity-70 mb-1">
|
||
⚠️ Подписка истекла {expiryDate}
|
||
</div>
|
||
) : (
|
||
<div className="text-sm opacity-70 mb-1">
|
||
⚠️ Нет подписки
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Action Buttons */}
|
||
<div className="w-full max-w-md space-y-3">
|
||
{/* Показываем trial только если нет активной подписки */}
|
||
{!hasSubscription && (
|
||
<ActionButton
|
||
icon={<Gift className="w-5 h-5" />}
|
||
text={isLoading ? "Активация..." : "Попробовать 7 дней бесплатно"}
|
||
onClick={handleActivateTrial}
|
||
/>
|
||
)}
|
||
<ActionButton
|
||
icon={<DollarSign className="w-5 h-5" />}
|
||
text="Купить подписку от 99₽"
|
||
onClick={() => (window.location.href = '/plans')}
|
||
/>
|
||
<Link href="/setup">
|
||
<ActionButton
|
||
icon={<Wrench className="w-5 h-5" />}
|
||
text="Настроить VPN"
|
||
onClick={() => {}}
|
||
/>
|
||
</Link>
|
||
</div>
|
||
</main>
|
||
|
||
{/* Bottom Navigation */}
|
||
<nav
|
||
className="border-t"
|
||
style={{ borderColor: 'var(--border)', background: 'var(--bg-card)' }}
|
||
>
|
||
<div className="flex items-center justify-around py-3">
|
||
<NavButton
|
||
icon={<User className="w-6 h-6" />}
|
||
label="Аккаунт"
|
||
onClick={() => setIsMenuOpen(true)}
|
||
/>
|
||
<NavButton
|
||
icon={<HelpCircle className="w-6 h-6" />}
|
||
label="Помощь"
|
||
href="/help"
|
||
/>
|
||
</div>
|
||
</nav>
|
||
|
||
{/* Account Menu Modal */}
|
||
{isMenuOpen && (
|
||
<div
|
||
className="fixed inset-0 z-50 flex items-end"
|
||
style={{ background: 'rgba(0, 0, 0, 0.5)' }}
|
||
onClick={() => setIsMenuOpen(false)}
|
||
>
|
||
<div
|
||
className="w-full rounded-t-3xl p-6 pb-8"
|
||
style={{ background: 'var(--bg-card)' }}
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between mb-6">
|
||
<h2 className="text-xl font-bold" style={{ color: 'var(--text-white)' }}>
|
||
Меню аккаунта
|
||
</h2>
|
||
<button
|
||
onClick={() => setIsMenuOpen(false)}
|
||
className="p-2 rounded-lg hover:opacity-70 transition-opacity"
|
||
style={{ background: 'var(--bg-elevated)' }}
|
||
>
|
||
<X className="w-5 h-5" style={{ color: 'var(--text-primary)' }} />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Menu Items */}
|
||
<div className="space-y-2">
|
||
{hasSubscription ? (
|
||
<>
|
||
<Link href={`/subscription/${subscriptionToken}`}>
|
||
<MenuButton
|
||
icon={<Shield className="w-5 h-5" />}
|
||
label="📊 Моя подписка"
|
||
onClick={() => setIsMenuOpen(false)}
|
||
/>
|
||
</Link>
|
||
|
||
<MenuButton
|
||
icon={<Key className="w-5 h-5" />}
|
||
label="🔑 Ключ подписки"
|
||
onClick={() => {
|
||
setIsMenuOpen(false);
|
||
setIsKeyMenuOpen(true);
|
||
}}
|
||
/>
|
||
</>
|
||
) : (
|
||
<Link href="/plans">
|
||
<MenuButton
|
||
icon={<Shield className="w-5 h-5" />}
|
||
label="🛒 Купить подписку"
|
||
onClick={() => setIsMenuOpen(false)}
|
||
/>
|
||
</Link>
|
||
)}
|
||
|
||
<Link href="/plans">
|
||
<MenuButton
|
||
icon={<CreditCard className="w-5 h-5" />}
|
||
label="💳 Тарифы"
|
||
onClick={() => setIsMenuOpen(false)}
|
||
/>
|
||
</Link>
|
||
|
||
<Link href="/settings">
|
||
<MenuButton
|
||
icon={<Settings className="w-5 h-5" />}
|
||
label="⚙️ Настройки"
|
||
onClick={() => setIsMenuOpen(false)}
|
||
/>
|
||
</Link>
|
||
|
||
<MenuButton
|
||
icon={<MessageCircle className="w-5 h-5" />}
|
||
label="💬 Поддержка"
|
||
onClick={() => {
|
||
setIsMenuOpen(false);
|
||
router.push('/help');
|
||
}}
|
||
/>
|
||
|
||
<MenuButton
|
||
icon={<UserPlus className="w-5 h-5" />}
|
||
label="🎁 Пригласить друга"
|
||
onClick={() => {
|
||
setIsMenuOpen(false);
|
||
setIsReferralOpen(true);
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Key Subscription Menu Modal */}
|
||
{isKeyMenuOpen && (
|
||
<div
|
||
className="fixed inset-0 z-50 flex items-end"
|
||
style={{ background: 'rgba(0, 0, 0, 0.5)' }}
|
||
onClick={() => setIsKeyMenuOpen(false)}
|
||
>
|
||
<div
|
||
className="w-full rounded-t-3xl p-6 pb-8"
|
||
style={{ background: 'var(--bg-card)' }}
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between mb-6">
|
||
<h2 className="text-xl font-bold" style={{ color: 'var(--text-white)' }}>
|
||
Ключ подписки
|
||
</h2>
|
||
<button
|
||
onClick={() => setIsKeyMenuOpen(false)}
|
||
className="p-2 rounded-lg hover:opacity-70 transition-opacity"
|
||
style={{ background: 'var(--bg-elevated)' }}
|
||
>
|
||
<X className="w-5 h-5" style={{ color: 'var(--text-primary)' }} />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Menu Items */}
|
||
<div className="space-y-2">
|
||
<MenuButton
|
||
icon={<Copy className="w-5 h-5" />}
|
||
label="Скопировать ссылку"
|
||
onClick={() => {
|
||
copyToClipboard(subscriptionUrl);
|
||
setIsKeyMenuOpen(false);
|
||
}}
|
||
/>
|
||
|
||
<MenuButton
|
||
icon={<QrCode className="w-5 h-5" />}
|
||
label="Показать QR код"
|
||
onClick={() => {
|
||
setIsKeyMenuOpen(false);
|
||
setIsQROpen(true);
|
||
}}
|
||
/>
|
||
|
||
<MenuButton
|
||
icon={<ExternalLink className="w-5 h-5" />}
|
||
label="Поделиться подпиской"
|
||
onClick={() => {
|
||
// TODO: Проверка тарифа, затем share sheet
|
||
if (navigator.share) {
|
||
navigator.share({
|
||
title: 'VPN подписка',
|
||
text: 'Моя VPN подписка',
|
||
url: subscriptionUrl,
|
||
});
|
||
} else {
|
||
copyToClipboard(subscriptionUrl);
|
||
}
|
||
setIsKeyMenuOpen(false);
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* QR Code Modal */}
|
||
<QRCodeModal
|
||
isOpen={isQROpen}
|
||
onClose={() => setIsQROpen(false)}
|
||
url={subscriptionUrl}
|
||
title="QR код подписки"
|
||
/>
|
||
|
||
{/* Referral Modal */}
|
||
<ReferralModal
|
||
isOpen={isReferralOpen}
|
||
onClose={() => setIsReferralOpen(false)}
|
||
referralUrl={`https://t.me/umbrix_bot?start=ref_${subscriptionToken?.split('_')[0] || 'DEMO'}`}
|
||
onShare={() => {
|
||
shareReferralLink();
|
||
setIsReferralOpen(false);
|
||
}}
|
||
onCopy={() => {
|
||
const userId = subscriptionToken?.split('_')[0] || 'DEMO';
|
||
const referralUrl = `https://t.me/umbrix_bot?start=ref_${userId}`;
|
||
copyToClipboard(referralUrl);
|
||
setIsReferralOpen(false);
|
||
}}
|
||
/>
|
||
|
||
{/* Setup Wizard Modal */}
|
||
{subscriptionToken && (
|
||
<SetupWizard
|
||
isOpen={isSetupWizardOpen}
|
||
onClose={() => setIsSetupWizardOpen(false)}
|
||
subscriptionUrl={getSubscriptionUrl(subscriptionToken)}
|
||
username={subscriptionToken}
|
||
planType="trial"
|
||
expiryDate={expiryDate}
|
||
/>
|
||
)}
|
||
|
||
{/* Toast Notification */}
|
||
{showToast && (
|
||
<div className="fixed bottom-24 left-1/2 transform -translate-x-1/2 z-50 animate-fade-in">
|
||
<div className="bg-slate-800 text-white px-6 py-3 rounded-full shadow-lg border border-slate-700 flex items-center gap-2">
|
||
<span>{toastMessage}</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function ActionButton({
|
||
icon,
|
||
text,
|
||
onClick,
|
||
}: {
|
||
icon: React.ReactNode;
|
||
text: string;
|
||
onClick: () => void;
|
||
}) {
|
||
return (
|
||
<button
|
||
onClick={onClick}
|
||
className="w-full flex items-center gap-3 p-4 rounded-xl transition-all hover:opacity-80"
|
||
style={{ background: 'var(--bg-card)', color: 'var(--text-white)' }}
|
||
>
|
||
<div style={{ color: 'var(--primary)' }}>{icon}</div>
|
||
<span className="font-medium">{text}</span>
|
||
</button>
|
||
);
|
||
}
|
||
|
||
function NavButton({
|
||
icon,
|
||
label,
|
||
href,
|
||
onClick,
|
||
}: {
|
||
icon: React.ReactNode;
|
||
label: string;
|
||
href?: string;
|
||
onClick?: () => void;
|
||
}) {
|
||
if (onClick) {
|
||
return (
|
||
<button
|
||
onClick={onClick}
|
||
className="flex flex-col items-center gap-1 transition-all hover:opacity-80 cursor-pointer"
|
||
style={{ color: 'var(--text-primary)' }}
|
||
>
|
||
{icon}
|
||
<span className="text-xs">{label}</span>
|
||
</button>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Link href={href!}>
|
||
<div className="flex flex-col items-center gap-1 transition-all hover:opacity-80 cursor-pointer"
|
||
style={{ color: 'var(--text-primary)' }}
|
||
>
|
||
{icon}
|
||
<span className="text-xs">{label}</span>
|
||
</div>
|
||
</Link>
|
||
);
|
||
}
|
||
|
||
function MenuButton({
|
||
icon,
|
||
label,
|
||
onClick,
|
||
}: {
|
||
icon: React.ReactNode;
|
||
label: string;
|
||
onClick: () => void;
|
||
}) {
|
||
return (
|
||
<button
|
||
onClick={onClick}
|
||
className="w-full flex items-center justify-between p-4 rounded-xl transition-all hover:opacity-80"
|
||
style={{ background: 'var(--bg-elevated)', color: 'var(--text-white)' }}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div style={{ color: 'var(--primary)' }}>{icon}</div>
|
||
<span className="font-medium">{label}</span>
|
||
</div>
|
||
<ChevronRight className="w-5 h-5 opacity-50" />
|
||
</button>
|
||
);
|
||
}
|
||
|