diff --git a/src/components/admin/userDetail/GiftsTab.tsx b/src/components/admin/userDetail/GiftsTab.tsx index b053aff..b64ed45 100644 --- a/src/components/admin/userDetail/GiftsTab.tsx +++ b/src/components/admin/userDetail/GiftsTab.tsx @@ -1,5 +1,6 @@ import { useTranslation } from 'react-i18next'; -import { SendIcon } from '@/components/icons'; +import { GiftIcon, SendIcon } from '@/components/icons'; +import { StatCard } from '@/components/stats'; import { useCurrency } from '../../../hooks/useCurrency'; import type { AdminUserGiftItem, AdminUserGiftsResponse } from '../../../api/adminUsers'; @@ -208,18 +209,18 @@ export function GiftsTab({ giftsLoading, giftsData, locale, onNavigateToUser }:
{/* Summary counters */}
-
-
- {t('admin.users.detail.gifts.totalSent')} -
-
{giftsData.sent_total}
-
-
-
- {t('admin.users.detail.gifts.totalReceived')} -
-
{giftsData.received_total}
-
+ } + tone="accent" + /> + } + tone="success" + />
{/* Sent Gifts */} diff --git a/src/components/admin/userDetail/ReferralsTab.tsx b/src/components/admin/userDetail/ReferralsTab.tsx index e64c801..964bc4b 100644 --- a/src/components/admin/userDetail/ReferralsTab.tsx +++ b/src/components/admin/userDetail/ReferralsTab.tsx @@ -5,7 +5,8 @@ import { useNavigate } from 'react-router'; import { useCurrency } from '../../../hooks/useCurrency'; import { useNotify } from '../../../platform/hooks/useNotify'; import { adminUsersApi, type UserDetailResponse, type UserListItem } from '../../../api/adminUsers'; -import { XIcon } from '@/components/icons'; +import { StatCard } from '@/components/stats'; +import { BanknotesIcon, PercentIcon, TagIcon, UsersIcon, XIcon } from '@/components/icons'; // ────────────────────────────────────────────────────────────────── // Referrals tab — top-of-graph referrer + stats + referrals list, @@ -327,40 +328,35 @@ export function ReferralsTab({ user, userId, onUserRefresh }: ReferralsTabProps) {/* Section 2: Referral stats */}
-
-
- {t('admin.users.detail.referrals.totalReferrals')} -
-
- {user.referral.referrals_count} -
-
-
-
- {t('admin.users.detail.referrals.totalEarnings')} -
-
- {formatWithCurrency(user.referral.total_earnings_kopeks / 100)} -
-
-
-
- {t('admin.users.detail.referrals.commission')} -
-
- {user.referral.commission_percent != null + } + tone="neutral" + /> + } + tone="neutral" + /> + -
-
-
- {t('admin.users.detail.referrals.referralCode')} -
-
- {user.referral.referral_code} -
-
+ : t('admin.users.detail.referrals.default') + } + icon={} + tone="neutral" + /> + } + tone="neutral" + valueClassName="font-mono" + />
{/* Section 3: Referrals list */} diff --git a/src/components/dashboard/StatsGrid.tsx b/src/components/dashboard/StatsGrid.tsx index 3a41585..7701566 100644 --- a/src/components/dashboard/StatsGrid.tsx +++ b/src/components/dashboard/StatsGrid.tsx @@ -1,9 +1,8 @@ -import { PiCaretRight } from 'react-icons/pi'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router'; import { useCurrency } from '../../hooks/useCurrency'; -import { useTheme } from '../../hooks/useTheme'; -import { getGlassColors } from '../../utils/glassTheme'; +import { StatCard } from '@/components/stats'; +import { CardIcon, ChevronRightIcon, UsersIcon } from '@/components/icons'; interface StatsGridProps { balanceRubles: number; @@ -12,10 +11,6 @@ interface StatsGridProps { refLoading: boolean; } -const ChevronIcon = ({ color }: { color: string }) => ( -
@@ -835,19 +795,19 @@ function OverviewTab({ label={t('admin.remnawave.overview.lifetimeTraffic', 'Трафик за всё время')} value={formatBytes(recap.total.traffic_bytes)} icon={} - color="purple" + tone="accent" /> } - color="blue" + tone="accent" /> } - color="green" + tone="success" /> } - color="accent" + tone="accent" />
@@ -936,26 +896,26 @@ function OverviewTab({ label={t('admin.remnawave.overview.panelRam', 'RAM процесса')} value={formatBytes(health.rss_bytes)} icon={} - color="blue" + tone="accent" /> } - color="purple" + tone="accent" /> } - color={health.event_loop_p99_ms > 50 ? 'red' : 'green'} + tone={health.event_loop_p99_ms > 50 ? 'error' : 'success'} /> } - color="accent" + tone="accent" /> @@ -1037,31 +997,31 @@ function NodesTab({ label={t('admin.remnawave.nodes.stats.total', 'Total')} value={stats.total} icon={} - color="accent" + tone="accent" /> } - color="green" + tone="success" /> } - color="red" + tone="error" /> } - color="accent" + tone="accent" /> } - color="blue" + tone="accent" /> @@ -1170,25 +1130,25 @@ function SquadsTab({ label={t('admin.remnawave.squads.stats.total', 'Total')} value={stats.total} icon={} - color="accent" + tone="accent" /> } - color="green" + tone="success" /> } - color="blue" + tone="accent" /> } - color="purple" + tone="accent" /> diff --git a/src/pages/AdminRemnawaveSquadDetail.tsx b/src/pages/AdminRemnawaveSquadDetail.tsx index 985767e..d4034c0 100644 --- a/src/pages/AdminRemnawaveSquadDetail.tsx +++ b/src/pages/AdminRemnawaveSquadDetail.tsx @@ -3,7 +3,8 @@ import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { adminRemnawaveApi, SquadWithLocalInfo } from '../api/adminRemnawave'; import { AdminBackButton } from '../components/admin'; -import { ServerIcon, UsersIcon, CheckIcon, XIcon } from '../components/icons'; +import { ServerIcon, UsersIcon, CheckIcon, XIcon, BanknotesIcon } from '../components/icons'; +import { StatCard } from '@/components/stats'; import Twemoji from 'react-twemoji'; import { getFlagEmoji } from '../utils/subscriptionHelpers'; @@ -126,43 +127,33 @@ export default function AdminRemnawaveSquadDetail() { {t('admin.remnawave.squads.statsTitle', 'Statistics')}
-
-
- - {t('admin.remnawave.squads.members', 'Members')} -
-

{squad.members_count}

-
-
-
- - {t('admin.remnawave.squads.inbounds', 'Inbounds')} -
-

{squad.inbounds_count}

-
+ } + tone="neutral" + /> + } + tone="neutral" + /> {squad.is_synced && ( <> -
-
- - {t('admin.remnawave.squads.users', 'Users')} -
-

- {squad.current_users ?? 0} - - {' '} - / {squad.max_users ?? '∞'} - -

-
-
-
- {t('admin.remnawave.squads.price', 'Price')} -
-

- {((squad.price_kopeks ?? 0) / 100).toFixed(0)} ₽ -

-
+ } + tone="neutral" + subValue={`/ ${squad.max_users ?? '∞'}`} + /> + } + tone="neutral" + /> )}
diff --git a/src/pages/AdminRoles.tsx b/src/pages/AdminRoles.tsx index 7407add..746e235 100644 --- a/src/pages/AdminRoles.tsx +++ b/src/pages/AdminRoles.tsx @@ -7,7 +7,16 @@ import { PermissionGate } from '@/components/auth/PermissionGate'; import { usePermissionStore } from '@/store/permissions'; import { usePlatform } from '@/platform/hooks/usePlatform'; import { useFocusTrap } from '@/hooks/useFocusTrap'; -import { BackIcon, PlusIcon, EditIcon, TrashIcon, ShieldIcon } from '@/components/icons'; +import { + BackIcon, + PlusIcon, + EditIcon, + TrashIcon, + ShieldIcon, + CheckCircleIcon, + CogIcon, +} from '@/components/icons'; +import { StatCard } from '@/components/stats'; export default function AdminRoles() { const { t } = useTranslation(); @@ -90,22 +99,24 @@ export default function AdminRoles() { {/* Stats Overview */} {sortedRoles.length > 0 && (
-
-
{sortedRoles.length}
-
{t('admin.roles.stats.totalRoles')}
-
-
-
- {sortedRoles.filter((r) => r.is_active).length} -
-
{t('admin.roles.stats.active')}
-
-
-
- {sortedRoles.filter((r) => r.is_system).length} -
-
{t('admin.roles.stats.system')}
-
+ } + tone="neutral" + /> + r.is_active).length} + icon={} + tone="accent" + /> + r.is_system).length} + icon={} + tone="warning" + />
)} diff --git a/src/pages/AdminTickets.tsx b/src/pages/AdminTickets.tsx index e779476..b87dec4 100644 --- a/src/pages/AdminTickets.tsx +++ b/src/pages/AdminTickets.tsx @@ -9,7 +9,17 @@ import { adminApi, AdminTicket, AdminTicketDetail } from '../api/admin'; import { ticketsApi } from '../api/tickets'; import { copyToClipboard as copyText } from '../utils/clipboard'; import { usePlatform } from '../platform/hooks/usePlatform'; -import { BackIcon, SettingsIcon, TicketIcon, XIcon } from '@/components/icons'; +import { + BackIcon, + CheckCircleIcon, + ClockIcon, + InboxIcon, + SettingsIcon, + TicketIcon, + XCircleIcon, + XIcon, +} from '@/components/icons'; +import { StatCard } from '@/components/stats'; interface MediaAttachment { id: string; @@ -278,25 +288,37 @@ export default function AdminTickets() { {/* Stats */} {stats && (
-
-
{stats.total}
-
{t('admin.tickets.total')}
-
-
-
{stats.open}
-
{t('admin.tickets.statusOpen')}
-
-
-
{stats.pending}
-
{t('admin.tickets.statusPending')}
-
-
-
{stats.answered}
-
{t('admin.tickets.statusAnswered')}
-
-
-
{stats.closed}
-
{t('admin.tickets.statusClosed')}
+ } + tone="neutral" + /> + } + tone="accent" + /> + } + tone="warning" + /> + } + tone="success" + /> +
+ } + tone="neutral" + />
)} diff --git a/src/pages/AdminUsers.tsx b/src/pages/AdminUsers.tsx index e32f2f4..315dd3b 100644 --- a/src/pages/AdminUsers.tsx +++ b/src/pages/AdminUsers.tsx @@ -5,6 +5,7 @@ import { useQuery } from '@tanstack/react-query'; import { useCurrency } from '../hooks/useCurrency'; import { adminUsersApi, type UserListItem } from '../api/adminUsers'; import { usePlatform } from '../platform/hooks/usePlatform'; +import { StatCard } from '@/components/stats'; import { BackIcon, SearchIcon, @@ -12,33 +13,13 @@ import { ChevronRightIcon, RefreshIcon, TelegramSmallIcon as TelegramIcon, + UsersIcon, + CheckCircleIcon, + SubscriptionIcon, + UserPlusIcon, + BanIcon, } from '@/components/icons'; -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', @@ -208,26 +189,35 @@ export default function AdminUsers() { {/* Stats */} {stats && (
- } + tone="accent" + /> + } + tone="success" /> } + tone="accent" /> } + tone="warning" /> } + tone="error" />
)} diff --git a/src/pages/AdminWithdrawals.tsx b/src/pages/AdminWithdrawals.tsx index e1bb16a..4fd042d 100644 --- a/src/pages/AdminWithdrawals.tsx +++ b/src/pages/AdminWithdrawals.tsx @@ -4,7 +4,8 @@ import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { withdrawalApi, AdminWithdrawalItem } from '../api/withdrawals'; import { AdminBackButton } from '../components/admin'; -import { ChevronRightIcon } from '@/components/icons'; +import { ChevronRightIcon, ClockIcon, WalletIcon } from '@/components/icons'; +import { StatCard } from '@/components/stats'; import { useCurrency } from '../hooks/useCurrency'; import { formatDate, getWithdrawalStatusBadge, getRiskColor } from '../utils/withdrawalUtils'; @@ -57,20 +58,18 @@ export default function AdminWithdrawals() { {/* Overview Stats */} {data && (
-
-
{pendingCount}
-
- {t('admin.withdrawals.overview.pendingCount')} -
-
-
-
- {formatWithCurrency(pendingTotal / 100, 0)} -
-
- {t('admin.withdrawals.overview.pendingAmount')} -
-
+ } + tone="warning" + /> + } + tone="warning" + />
)} diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index e669e67..1a3657c 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -10,15 +10,23 @@ import { partnerApi } from '../api/partners'; import { withdrawalApi } from '../api/withdrawals'; import { CampaignCard } from '../components/partner/CampaignCard'; import { useCurrency } from '../hooks/useCurrency'; +import { StatCard } from '@/components/stats'; import { + ArrowDownIcon, + ArrowUpIcon, + BanknotesIcon, + CardIcon, CheckIcon, ClockIcon, CopyIcon, ExclamationIcon, + GiftIcon, LinkIcon, PartnerIcon, + PercentIcon, ShareIcon, TelegramIcon, + UserPlusIcon, UsersIcon, WalletIcon, } from '@/components/icons'; @@ -131,33 +139,33 @@ export default function Referral() {

{t('referral.terms.title')}

-
-
{t('referral.terms.commission')}
-
- {terms.commission_percent}% -
-
-
-
{t('referral.terms.minTopup')}
-
- {formatAmount(terms.minimum_topup_rubles)} {currencySymbol} -
-
+ } + tone="neutral" + /> + } + tone="neutral" + /> {showNewUserBonus && ( -
-
{t('referral.terms.newUserBonus')}
-
- {formatPositive(terms.first_topup_bonus_rubles)} -
-
+ } + tone="success" + /> )} {showInviterBonus && ( -
-
{t('referral.terms.inviterBonus')}
-
- {formatPositive(terms.inviter_bonus_rubles)} -
-
+ } + tone="success" + /> )}
@@ -237,23 +245,27 @@ export default function Referral() { {/* Stats Cards */}
-
-
{t('referral.stats.totalReferrals')}
-
{info?.total_referrals || 0}
-
- {info?.active_referrals || 0} {t('referral.stats.activeReferrals').toLowerCase()} -
-
-
-
{t('referral.stats.totalEarnings')}
-
- {formatPositive(info?.total_earnings_rubles || 0)} -
-
-
-
{t('referral.stats.commissionRate')}
-
{info?.commission_percent || 0}%
+
+ } + tone="neutral" + subValue={`${info?.active_referrals || 0} ${t('referral.stats.activeReferrals').toLowerCase()}`} + />
+ } + tone="success" + /> + } + tone="accent" + />
{/* Referral Links */} @@ -545,38 +557,38 @@ export default function Referral() {
-
-
{t('referral.withdrawal.available')}
-
- {formatWithCurrency(withdrawalBalance.available_total / 100)} -
-
-
-
- {t('referral.withdrawal.totalEarned')} -
-
- {formatWithCurrency(withdrawalBalance.total_earned / 100)} -
-
-
-
{t('referral.withdrawal.withdrawn')}
-
- {formatWithCurrency(withdrawalBalance.withdrawn / 100)} -
-
-
-
{t('referral.withdrawal.spent')}
-
- {formatWithCurrency(withdrawalBalance.referral_spent / 100)} -
-
-
-
{t('referral.withdrawal.pending')}
-
- {formatWithCurrency(withdrawalBalance.pending / 100)} -
+
+ } + tone="success" + />
+ } + tone="neutral" + /> + } + tone="neutral" + /> + } + tone="neutral" + /> + } + tone="warning" + />
diff --git a/src/pages/ReferralNetwork/components/NetworkStats.tsx b/src/pages/ReferralNetwork/components/NetworkStats.tsx index cbc5a25..b95ef94 100644 --- a/src/pages/ReferralNetwork/components/NetworkStats.tsx +++ b/src/pages/ReferralNetwork/components/NetworkStats.tsx @@ -1,4 +1,12 @@ import { useTranslation } from 'react-i18next'; +import { StatCard } from '@/components/stats'; +import { + BanknotesIcon, + CampaignIcon, + PartnerIcon, + UsersIcon, + WalletIcon, +} from '@/components/icons'; import type { NetworkGraphData } from '@/types/referralNetwork'; import { formatKopeksToRubles } from '../utils'; @@ -15,45 +23,37 @@ export function NetworkStats({ data, className }: NetworkStatsProps) { className={`rounded-xl border border-dark-700/50 bg-dark-900/80 p-2 backdrop-blur-md sm:p-3 ${className ?? ''}`} >
-
-

- {t('admin.referralNetwork.stats.totalUsers')} -

-

- {data.total_users.toLocaleString()} -

-
-
-

- {t('admin.referralNetwork.stats.totalReferrers')} -

-

- {data.total_referrers.toLocaleString()} -

-
-
-

- {t('admin.referralNetwork.stats.totalCampaigns')} -

-

- {data.total_campaigns.toLocaleString()} -

-
-
-

- {t('admin.referralNetwork.stats.subscriptionRevenue')} -

-

- {formatKopeksToRubles(data.total_subscription_revenue_kopeks)} ₽ -

-
-
-

- {t('admin.referralNetwork.stats.totalEarnings')} -

-

- {formatKopeksToRubles(data.total_earnings_kopeks)} ₽ -

+ } + tone="neutral" + /> + } + tone="neutral" + /> + } + tone="neutral" + /> + } + tone="accent" + /> +
+ } + tone="neutral" + />