From 803609c1fbeaf6dbb3c1568987120e6d076a7cda Mon Sep 17 00:00:00 2001 From: c0mrade Date: Mon, 2 Feb 2026 10:03:47 +0300 Subject: [PATCH] refactor: consolidate icons and move server edit to separate page - Create centralized icons file at src/components/icons/index.tsx - Update admin and AppShell icons to re-export from centralized location - Replace trial "T" button with GiftIcon in AdminServers - Move server editing from modal to dedicated page /admin/servers/:id/edit - Add localization keys for server edit page (mainSettings, pricingAndLimits, updateError) --- src/App.tsx | 11 + src/components/PromoOffersSection.tsx | 18 +- src/components/admin/icons.tsx | 159 +------ src/components/icons/index.tsx | 560 +++++++++++++++++++++++ src/components/layout/AppShell/icons.tsx | 354 +------------- src/locales/en.json | 5 +- src/locales/fa.json | 5 +- src/locales/ru.json | 5 +- src/locales/zh.json | 5 +- src/pages/AdminServerEdit.tsx | 341 ++++++++++++++ src/pages/AdminServers.tsx | 330 +------------ 11 files changed, 980 insertions(+), 813 deletions(-) create mode 100644 src/components/icons/index.tsx create mode 100644 src/pages/AdminServerEdit.tsx diff --git a/src/App.tsx b/src/App.tsx index 9fa8309..0efbfe4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,6 +37,7 @@ const AdminWheel = lazy(() => import('./pages/AdminWheel')); const AdminTariffs = lazy(() => import('./pages/AdminTariffs')); const AdminTariffCreate = lazy(() => import('./pages/AdminTariffCreate')); const AdminServers = lazy(() => import('./pages/AdminServers')); +const AdminServerEdit = lazy(() => import('./pages/AdminServerEdit')); const AdminDashboard = lazy(() => import('./pages/AdminDashboard')); const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem')); const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts')); @@ -338,6 +339,16 @@ function App() { } /> + + + + + + } + /> ( - - - -); - -const CheckIcon = () => ( - - - -); +import { ClockIcon, CheckIcon } from './icons'; // Helper functions const formatTimeLeft = ( diff --git a/src/components/admin/icons.tsx b/src/components/admin/icons.tsx index f1d1ff1..213ce49 100644 --- a/src/components/admin/icons.tsx +++ b/src/components/admin/icons.tsx @@ -1,141 +1,18 @@ -// Admin Settings Icons - -export const BackIcon = () => ( - - - -); - -export const SearchIcon = () => ( - - - -); - -export const StarIcon = ({ filled }: { filled?: boolean }) => ( - - - -); - -export const ChevronDownIcon = () => ( - - - -); - -export const UploadIcon = () => ( - - - -); - -export const TrashIcon = () => ( - - - -); - -export const PencilIcon = () => ( - - - -); - -export const RefreshIcon = () => ( - - - -); - -export const LockIcon = () => ( - - - -); - -export const CheckIcon = () => ( - - - -); - -export const CloseIcon = () => ( - - - -); - -export const SunIcon = () => ( - - - -); - -export const MoonIcon = () => ( - - - -); - -export const MenuIcon = () => ( - - - -); - -export const EditIcon = () => ( - - - -); +// Re-export from centralized icons +export { + BackIcon, + SearchIcon, + StarIcon, + ChevronDownIcon, + UploadIcon, + TrashIcon, + PencilIcon, + RefreshIcon, + LockIcon, + CheckIcon, + CloseIcon, + SunIcon, + MoonIcon, + MenuIcon, + EditIcon, +} from '../icons'; diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx new file mode 100644 index 0000000..15b2b65 --- /dev/null +++ b/src/components/icons/index.tsx @@ -0,0 +1,560 @@ +import { cn } from '@/lib/utils'; + +interface IconProps { + className?: string; +} + +// Navigation & Layout +export const HomeIcon = ({ className }: IconProps) => ( + + + +); + +export const BackIcon = ({ className }: IconProps) => ( + + + +); + +export const MenuIcon = ({ className }: IconProps) => ( + + + +); + +export const CloseIcon = ({ className }: IconProps) => ( + + + +); + +export const ChevronDownIcon = ({ className }: IconProps) => ( + + + +); + +export const ArrowRightIcon = ({ className }: IconProps) => ( + + + +); + +// Actions +export const SearchIcon = ({ className }: IconProps) => ( + + + +); + +export const PlusIcon = ({ className }: IconProps) => ( + + + +); + +export const EditIcon = ({ className }: IconProps) => ( + + + +); + +export const PencilIcon = ({ className }: IconProps) => ( + + + +); + +export const TrashIcon = ({ className }: IconProps) => ( + + + +); + +export const UploadIcon = ({ className }: IconProps) => ( + + + +); + +export const DownloadIcon = ({ className }: IconProps) => ( + + + +); + +export const RefreshIcon = ({ className }: IconProps) => ( + + + +); + +export const SyncIcon = ({ className }: IconProps) => ( + + + +); + +// Status +export const CheckIcon = ({ className }: IconProps) => ( + + + +); + +export const XIcon = ({ className }: IconProps) => ( + + + +); + +export const LockIcon = ({ className }: IconProps) => ( + + + +); + +export const InfoIcon = ({ className }: IconProps) => ( + + + +); + +// User & People +export const UserIcon = ({ className }: IconProps) => ( + + + +); + +export const UsersIcon = ({ className }: IconProps) => ( + + + +); + +export const LogoutIcon = ({ className }: IconProps) => ( + + + +); + +// Theme +export const SunIcon = ({ className }: IconProps) => ( + + + +); + +export const MoonIcon = ({ className }: IconProps) => ( + + + +); + +export const PaletteIcon = ({ className }: IconProps) => ( + + + +); + +// Features & Content +export const SubscriptionIcon = ({ className }: IconProps) => ( + + + +); + +export const WalletIcon = ({ className }: IconProps) => ( + + + +); + +export const ChatIcon = ({ className }: IconProps) => ( + + + +); + +export const GiftIcon = ({ className }: IconProps) => ( + + + +); + +export const ClockIcon = ({ className }: IconProps) => ( + + + +); + +export const StarIcon = ({ className, filled }: IconProps & { filled?: boolean }) => ( + + + +); + +export const GamepadIcon = ({ className }: IconProps) => ( + + + +); + +export const ClipboardIcon = ({ className }: IconProps) => ( + + + +); + +export const CogIcon = ({ className }: IconProps) => ( + + + + +); + +export const WheelIcon = ({ className }: IconProps) => ( + + + +); + +export const ServerIcon = ({ className }: IconProps) => ( + + + +); diff --git a/src/components/layout/AppShell/icons.tsx b/src/components/layout/AppShell/icons.tsx index acfd41b..d8678c9 100644 --- a/src/components/layout/AppShell/icons.tsx +++ b/src/components/layout/AppShell/icons.tsx @@ -1,330 +1,24 @@ -import { cn } from '@/lib/utils'; - -interface IconProps { - className?: string; -} - -export const HomeIcon = ({ className }: IconProps) => ( - - - -); - -export const SubscriptionIcon = ({ className }: IconProps) => ( - - - -); - -export const WalletIcon = ({ className }: IconProps) => ( - - - -); - -export const UsersIcon = ({ className }: IconProps) => ( - - - -); - -export const ChatIcon = ({ className }: IconProps) => ( - - - -); - -export const UserIcon = ({ className }: IconProps) => ( - - - -); - -export const LogoutIcon = ({ className }: IconProps) => ( - - - -); - -export const SunIcon = ({ className }: IconProps) => ( - - - -); - -export const MoonIcon = ({ className }: IconProps) => ( - - - -); - -export const MenuIcon = ({ className }: IconProps) => ( - - - -); - -export const CloseIcon = ({ className }: IconProps) => ( - - - -); - -export const GamepadIcon = ({ className }: IconProps) => ( - - - -); - -export const ClipboardIcon = ({ className }: IconProps) => ( - - - -); - -export const InfoIcon = ({ className }: IconProps) => ( - - - -); - -export const CogIcon = ({ className }: IconProps) => ( - - - - -); - -export const WheelIcon = ({ className }: IconProps) => ( - - - -); - -export const SearchIcon = ({ className }: IconProps) => ( - - - -); - -export const PlusIcon = ({ className }: IconProps) => ( - - - -); - -export const ArrowRightIcon = ({ className }: IconProps) => ( - - - -); - -export const DownloadIcon = ({ className }: IconProps) => ( - - - -); - -export const PaletteIcon = ({ className }: IconProps) => ( - - - -); +// Re-export from centralized icons +export { + HomeIcon, + SubscriptionIcon, + WalletIcon, + UsersIcon, + ChatIcon, + UserIcon, + LogoutIcon, + SunIcon, + MoonIcon, + MenuIcon, + CloseIcon, + GamepadIcon, + ClipboardIcon, + InfoIcon, + CogIcon, + WheelIcon, + SearchIcon, + PlusIcon, + ArrowRightIcon, + DownloadIcon, + PaletteIcon, +} from '../../icons'; diff --git a/src/locales/en.json b/src/locales/en.json index 3fe4c4a..964487d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1382,7 +1382,10 @@ "enable": "Enable", "disable": "Disable", "toggleTrial": "Toggle Trial", - "loadError": "Failed to load server" + "loadError": "Failed to load server", + "mainSettings": "Main Settings", + "pricingAndLimits": "Pricing & Limits", + "updateError": "Failed to save changes" }, "campaigns": { "title": "Ad Campaigns", diff --git a/src/locales/fa.json b/src/locales/fa.json index 9d8ee7a..aabfbc4 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1137,7 +1137,10 @@ "enable": "فعال", "disable": "غیرفعال", "toggleTrial": "تغییر آزمایشی", - "loadError": "بارگذاری سرور ناموفق بود" + "loadError": "بارگذاری سرور ناموفق بود", + "mainSettings": "تنظیمات اصلی", + "pricingAndLimits": "قیمت‌گذاری و محدودیت‌ها", + "updateError": "ذخیره تغییرات ناموفق بود" }, "paymentMethods": { "title": "روش‌های پرداخت", diff --git a/src/locales/ru.json b/src/locales/ru.json index 99e7aa7..f9c89a1 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1893,7 +1893,10 @@ "enable": "Включить", "disable": "Отключить", "toggleTrial": "Переключить триал", - "loadError": "Не удалось загрузить сервер" + "loadError": "Не удалось загрузить сервер", + "mainSettings": "Основные настройки", + "pricingAndLimits": "Цены и лимиты", + "updateError": "Не удалось сохранить изменения" }, "campaigns": { "title": "Рекламные кампании", diff --git a/src/locales/zh.json b/src/locales/zh.json index 29724c1..91889ca 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1174,7 +1174,10 @@ "enable": "启用", "disable": "禁用", "toggleTrial": "切换试用", - "loadError": "加载服务器失败" + "loadError": "加载服务器失败", + "mainSettings": "基本设置", + "pricingAndLimits": "价格和限制", + "updateError": "保存更改失败" }, "campaigns": { "title": "广告活动", diff --git a/src/pages/AdminServerEdit.tsx b/src/pages/AdminServerEdit.tsx new file mode 100644 index 0000000..9dea960 --- /dev/null +++ b/src/pages/AdminServerEdit.tsx @@ -0,0 +1,341 @@ +import { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; +import { serversApi, ServerUpdateRequest } from '../api/servers'; +import { AdminBackButton } from '../components/admin'; +import { ServerIcon } from '../components/icons'; +import { createNumberInputHandler, toNumber } from '../utils/inputHelpers'; + +// Country flags (simple emoji mapping) +const getCountryFlag = (code: string | null): string => { + if (!code) return ''; + const codeMap: Record = { + RU: '🇷🇺', + US: '🇺🇸', + DE: '🇩🇪', + NL: '🇳🇱', + GB: '🇬🇧', + FR: '🇫🇷', + FI: '🇫🇮', + SE: '🇸🇪', + PL: '🇵🇱', + CZ: '🇨🇿', + AT: '🇦🇹', + CH: '🇨🇭', + UA: '🇺🇦', + KZ: '🇰🇿', + JP: '🇯🇵', + KR: '🇰🇷', + SG: '🇸🇬', + HK: '🇭🇰', + CA: '🇨🇦', + AU: '🇦🇺', + BR: '🇧🇷', + IN: '🇮🇳', + TR: '🇹🇷', + IL: '🇮🇱', + AE: '🇦🇪', + }; + return codeMap[code.toUpperCase()] || code; +}; + +export default function AdminServerEdit() { + const { t } = useTranslation(); + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const serverId = parseInt(id || '0'); + + const { + data: server, + isLoading, + error, + } = useQuery({ + queryKey: ['admin-server', serverId], + queryFn: () => serversApi.getServer(serverId), + enabled: serverId > 0, + }); + + const [displayName, setDisplayName] = useState(''); + const [description, setDescription] = useState(''); + const [countryCode, setCountryCode] = useState(''); + const [priceKopeks, setPriceKopeks] = useState(0); + const [maxUsers, setMaxUsers] = useState(null); + const [sortOrder, setSortOrder] = useState(0); + + useEffect(() => { + if (server) { + setDisplayName(server.display_name); + setDescription(server.description || ''); + setCountryCode(server.country_code || ''); + setPriceKopeks(server.price_kopeks); + setMaxUsers(server.max_users); + setSortOrder(server.sort_order); + } + }, [server]); + + const updateMutation = useMutation({ + mutationFn: (data: ServerUpdateRequest) => serversApi.updateServer(serverId, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['admin-servers'] }); + queryClient.invalidateQueries({ queryKey: ['admin-server', serverId] }); + navigate('/admin/servers'); + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const data: ServerUpdateRequest = { + display_name: displayName, + description: description || undefined, + country_code: countryCode || undefined, + price_kopeks: toNumber(priceKopeks), + max_users: maxUsers || undefined, + sort_order: toNumber(sortOrder), + }; + updateMutation.mutate(data); + }; + + if (isLoading) { + return ( +
+
+
+ ); + } + + if (error || !server) { + return ( +
+
+ +

{t('admin.servers.edit')}

+
+
+

{t('admin.servers.loadError')}

+ +
+
+ ); + } + + return ( +
+ {/* Header */} +
+ +
+ {getCountryFlag(server.country_code)} +
+ +
+
+
+

{t('admin.servers.edit')}

+

{server.display_name}

+
+
+ +
+ {/* Main Settings */} +
+

+ {t('admin.servers.mainSettings')} +

+ + {/* Original Name (readonly) */} +
+ +
+ {server.original_name || server.squad_uuid} +
+
+ + {/* Display Name */} +
+ + setDisplayName(e.target.value)} + className="input" + placeholder={t('admin.servers.displayNamePlaceholder')} + /> +
+ + {/* Description */} +
+ +