import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useCurrency } from '../hooks/useCurrency'; import { adminUsersApi, type UserListItem, type UsersStatsResponse } from '../api/adminUsers'; import { usePlatform } from '../platform/hooks/usePlatform'; // ============ Icons ============ const BackIcon = () => ( ); const SearchIcon = () => ( ); const ChevronLeftIcon = () => ( ); const ChevronRightIcon = () => ( ); const RefreshIcon = ({ className = 'w-5 h-5' }: { className?: string }) => ( ); const TelegramIcon = () => ( ); // ============ Components ============ interface StatCardProps { title: string; value: string | number; subtitle?: string; color: 'blue' | 'green' | 'yellow' | 'red' | 'purple'; } function StatCard({ title, value, subtitle, color }: StatCardProps) { const colors = { blue: 'bg-accent-500/20 text-accent-400 border-accent-500/30', green: 'bg-success-500/20 text-success-400 border-success-500/30', yellow: 'bg-warning-500/20 text-warning-400 border-warning-500/30', red: 'bg-error-500/20 text-error-400 border-error-500/30', purple: 'bg-accent-500/20 text-accent-400 border-accent-500/30', }; return (
{value}
{title}
{subtitle &&
{subtitle}
}
); } function StatusBadge({ status }: { status: string }) { const styles: Record = { active: 'bg-success-500/20 text-success-400 border-success-500/30', blocked: 'bg-error-500/20 text-error-400 border-error-500/30', deleted: 'bg-dark-600 text-dark-400 border-dark-500', trial: 'bg-accent-500/20 text-accent-400 border-accent-500/30', expired: 'bg-warning-500/20 text-warning-400 border-warning-500/30', disabled: 'bg-dark-600 text-dark-400 border-dark-500', }; return ( {status} ); } // ============ User List Component ============ interface UserRowProps { user: UserListItem; onClick: () => void; formatAmount: (rubAmount: number) => string; } function UserRow({ user, onClick, formatAmount }: UserRowProps) { const { t } = useTranslation(); return (
{/* Avatar */}
{user.first_name?.[0] || user.username?.[0] || '?'}
{/* Info - flex column on mobile, row on desktop */}
{/* Name and username */}
{user.full_name} {user.username && ( @{user.username} )}
{/* Telegram ID - full width on mobile */}
{user.telegram_id}
{/* Status badges - wrap on mobile */}
{user.status !== 'active' && } {user.has_subscription && user.subscription_status && ( {user.subscription_status === 'active' ? t('admin.users.status.subscription') : user.subscription_status === 'trial' ? t('admin.users.status.trial') : t('admin.users.status.expired')} )}
{/* Balance - smaller on mobile, show inline */}
{formatAmount(user.balance_rubles)}
{user.purchase_count > 0 ? t('admin.users.purchaseCount', { count: user.purchase_count }) : t('admin.users.noPurchases')}
); } // ============ Main Page ============ export default function AdminUsers() { const { t } = useTranslation(); const { formatWithCurrency } = useCurrency(); const navigate = useNavigate(); const { capabilities } = usePlatform(); const [users, setUsers] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [emailSearch, setEmailSearch] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [sortBy, setSortBy] = useState('created_at'); const [offset, setOffset] = useState(0); const [total, setTotal] = useState(0); const limit = 20; const loadUsers = useCallback(async () => { try { setLoading(true); const params: Record = { offset, limit, sort_by: sortBy }; if (search) params.search = search; if (emailSearch) params.email = emailSearch; if (statusFilter) params.status = statusFilter; const data = await adminUsersApi.getUsers( params as Parameters[0], ); setUsers(data.users); setTotal(data.total); } catch (error) { console.error('Failed to load users:', error); } finally { setLoading(false); } }, [offset, search, emailSearch, statusFilter, sortBy]); const loadStats = useCallback(async () => { try { const data = await adminUsersApi.getStats(); setStats(data); } catch (error) { console.error('Failed to load stats:', error); } }, []); useEffect(() => { loadUsers(); }, [loadUsers]); useEffect(() => { loadStats(); }, [loadStats]); const handleSearch = (e: React.FormEvent) => { e.preventDefault(); setOffset(0); loadUsers(); }; const totalPages = Math.ceil(total / limit); const currentPage = Math.floor(offset / limit) + 1; return (
{/* Header */}
{/* Show back button only on web, not in Telegram Mini App */} {!capabilities.hasBackButton && ( )}

{t('admin.users.title')}

{t('admin.users.subtitle')}

{/* Stats */} {stats && (
)} {/* Filters */}
{/* Search fields row */}
setSearch(e.target.value)} placeholder={t('admin.users.search')} className="w-full rounded-xl border border-dark-700 bg-dark-800 py-2 pl-10 pr-4 text-dark-100 placeholder-dark-500 focus:border-dark-600 focus:outline-none" />
setEmailSearch(e.target.value)} placeholder={t('admin.users.searchEmail')} className="w-full rounded-xl border border-dark-700 bg-dark-800 py-2 pl-10 pr-4 text-dark-100 placeholder-dark-500 focus:border-dark-600 focus:outline-none" />
{/* Filters row */}
{/* Users list */}
{loading ? (
) : users.length === 0 ? (
{t('admin.users.noData')}
) : ( users.map((user) => ( navigate(`/admin/users/${user.id}`)} formatAmount={(amount) => formatWithCurrency(amount)} /> )) )}
{/* Pagination */} {totalPages > 1 && (
{t('admin.users.pagination.showing', { from: offset + 1, to: Math.min(offset + limit, total), total, })}
{currentPage} / {totalPages}
)}
); }