import { useState } from 'react'; import { useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { useCurrency } from '../hooks/useCurrency'; import { adminUsersApi, type UserListItem } from '../api/adminUsers'; import { usePlatform } from '../platform/hooks/usePlatform'; const BackIcon = () => ( ); const SearchIcon = () => ( ); const ChevronLeftIcon = () => ( ); const ChevronRightIcon = () => ( ); const RefreshIcon = ({ className = 'w-5 h-5' }: { className?: string }) => ( ); const TelegramIcon = () => ( ); 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} ); } 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') : user.subscription_status === 'limited' ? t('subscription.trafficLimited') : 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')}
); } export default function AdminUsers() { const { t } = useTranslation(); const { formatWithCurrency } = useCurrency(); const navigate = useNavigate(); const { capabilities } = usePlatform(); const [search, setSearch] = useState(''); const [emailSearch, setEmailSearch] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [sortBy, setSortBy] = useState('created_at'); const [offset, setOffset] = useState(0); const limit = 20; const usersQuery = useQuery({ queryKey: ['admin-users', offset, limit, sortBy, search, emailSearch, statusFilter] as const, queryFn: () => { const params: Record = { offset, limit, sort_by: sortBy }; if (search) params.search = search; if (emailSearch) params.email = emailSearch; if (statusFilter) params.status = statusFilter; return adminUsersApi.getUsers(params as Parameters[0]); }, }); const users = usersQuery.data?.users ?? []; const total = usersQuery.data?.total ?? 0; const loading = usersQuery.isLoading; const statsQuery = useQuery({ queryKey: ['admin-users-stats'] as const, queryFn: () => adminUsersApi.getStats(), }); const stats = statsQuery.data ?? null; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); }; 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); setOffset(0); }} 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); setOffset(0); }} 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}
)}
); }