From d0e0b6b7e3fd29a533636b397ed5021a75074615 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Sun, 31 May 2026 18:50:50 +0300 Subject: [PATCH] feat(cabinet): migrate all icons to the panel's Phosphor set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the cabinet's hand-written heroicons-style SVG icon components with the Remnawave panel's own icon family — Phosphor via react-icons/pi (Duotone). All icons now live in a single central barrel (src/components/icons: index + extended-icons + editor-icons, 144 icons); every feature file imports from it instead of redefining inline SVGs (~440 icon defs removed). - Add react-icons dependency - Desktop nav (AppShell) now uses the central Phosphor icons, removing a stroke-vs-duotone inconsistency the partial migration introduced - Icons with custom props (SortIcon's direction, expandable chevrons) kept as thin Phosphor wrappers; the RemnawaveIcon brand logo and animated SVGs (checkmarks, spinners) are intentionally left as-is - Restore the original per-call-site icon sizes wherever the centralized default differed (211 call sites) --- package-lock.json | 10 + package.json | 1 + src/components/PromoOffersSection.tsx | 15 +- src/components/SuccessNotificationModal.tsx | 93 +-- src/components/TicketNotificationBell.tsx | 17 +- src/components/admin/MenuEditorTab.tsx | 101 +-- .../admin/SortableSelectedMethodCard.tsx | 11 +- .../admin/bulkActions/ActionModal.tsx | 23 +- .../admin/bulkActions/DropdownSelect.tsx | 12 +- .../admin/trafficUsage/TrafficIcons.tsx | 189 +---- .../trafficUsage/filters/CountryFilter.tsx | 4 +- .../admin/trafficUsage/filters/NodeFilter.tsx | 4 +- .../trafficUsage/filters/PeriodSelector.tsx | 6 +- .../trafficUsage/filters/StatusFilter.tsx | 4 +- .../trafficUsage/filters/TariffFilter.tsx | 4 +- .../admin/userDetail/BalanceTab.tsx | 21 +- .../admin/userDetail/SubscriptionTab.tsx | 27 +- src/components/admin/userDetail/SyncTab.tsx | 17 +- .../connection/InstallationGuide.tsx | 9 +- .../connection/blocks/BlockButtons.tsx | 17 +- src/components/dashboard/StatsGrid.tsx | 18 +- .../dashboard/SubscriptionCardActive.tsx | 18 +- src/components/icons/editor-icons.tsx | 85 +++ src/components/icons/extended-icons.tsx | 415 +++++++++++ src/components/icons/index.tsx | 694 ++++-------------- src/components/layout/AppShell/AppShell.tsx | 176 +---- src/components/news/NewsSection.tsx | 14 +- src/components/partner/CampaignCard.tsx | 28 +- src/components/primitives/Command/Command.tsx | 14 +- src/components/primitives/Dialog/Dialog.tsx | 14 +- .../primitives/DropdownMenu/DropdownMenu.tsx | 32 +- src/components/primitives/Popover/Popover.tsx | 14 +- src/components/primitives/Select/Select.tsx | 26 +- src/components/primitives/Sheet/Sheet.tsx | 14 +- src/pages/AdminAuditLog.tsx | 80 +- src/pages/AdminBanSystem.tsx | 162 +--- src/pages/AdminBroadcastCreate.tsx | 111 +-- src/pages/AdminBroadcastDetail.tsx | 79 +- src/pages/AdminBroadcasts.tsx | 81 +- src/pages/AdminBulkActions.tsx | 89 +-- src/pages/AdminCampaignCreate.tsx | 31 +- src/pages/AdminCampaignStats.tsx | 48 +- src/pages/AdminCampaigns.tsx | 23 +- src/pages/AdminChannelSubscriptions.tsx | 99 +-- src/pages/AdminDashboard.tsx | 171 +---- src/pages/AdminEmailTemplates.tsx | 60 +- src/pages/AdminInfoPageEditor.tsx | 182 ++--- src/pages/AdminInfoPages.tsx | 87 +-- src/pages/AdminLandingEditor.tsx | 11 +- src/pages/AdminLandingStats.tsx | 96 +-- src/pages/AdminLandings.tsx | 78 +- src/pages/AdminNews.tsx | 113 +-- src/pages/AdminNewsCreate.tsx | 150 +--- src/pages/AdminPanel.tsx | 446 +++-------- src/pages/AdminPartnerSettings.tsx | 14 +- src/pages/AdminPaymentMethodEdit.tsx | 30 +- src/pages/AdminPaymentMethods.tsx | 40 +- src/pages/AdminPayments.tsx | 38 +- src/pages/AdminPinnedMessageCreate.tsx | 59 +- src/pages/AdminPinnedMessages.tsx | 151 +--- src/pages/AdminPolicies.tsx | 109 +-- src/pages/AdminPromoGroupCreate.tsx | 38 +- src/pages/AdminPromoGroups.tsx | 52 +- src/pages/AdminPromoOfferSend.tsx | 65 +- src/pages/AdminPromoOffers.tsx | 58 +- src/pages/AdminPromocodeCreate.tsx | 32 +- src/pages/AdminPromocodeStats.tsx | 36 +- src/pages/AdminPromocodes.tsx | 77 +- src/pages/AdminRoleAssign.tsx | 81 +- src/pages/AdminRoleEdit.tsx | 15 +- src/pages/AdminRoles.tsx | 51 +- src/pages/AdminServers.tsx | 23 +- src/pages/AdminSettings.tsx | 29 +- src/pages/AdminTariffCreate.tsx | 92 +-- src/pages/AdminTariffs.tsx | 96 +-- src/pages/AdminTicketSettings.tsx | 14 +- src/pages/AdminTickets.tsx | 14 +- src/pages/AdminTrafficUsage.tsx | 8 +- src/pages/AdminUpdates.tsx | 102 +-- src/pages/AdminUserDetail.tsx | 18 +- src/pages/AdminUsers.tsx | 58 +- src/pages/AdminWheel.tsx | 164 +---- src/pages/Balance.tsx | 18 +- src/pages/Contests.tsx | 25 +- src/pages/Dashboard.tsx | 7 +- src/pages/Info.tsx | 64 +- src/pages/InfoPageView.tsx | 43 +- src/pages/NewsArticle.tsx | 38 +- src/pages/Polls.tsx | 37 +- src/pages/Profile.tsx | 45 +- src/pages/Referral.tsx | 80 +- src/pages/Support.tsx | 39 +- src/pages/TopUpAmount.tsx | 74 +- src/pages/Wheel.tsx | 49 +- 94 files changed, 1368 insertions(+), 5059 deletions(-) create mode 100644 src/components/icons/editor-icons.tsx create mode 100644 src/components/icons/extended-icons.tsx diff --git a/package-lock.json b/package-lock.json index bb32dd9..867b7de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "react-i18next": "^16.5.4", + "react-icons": "^5.6.0", "react-router": "^7.13.0", "react-twemoji": "^0.7.2", "recharts": "^3.7.0", @@ -6908,6 +6909,15 @@ } } }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", diff --git a/package.json b/package.json index e91c2f8..c3c0f2a 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "react-i18next": "^16.5.4", + "react-icons": "^5.6.0", "react-router": "^7.13.0", "react-twemoji": "^0.7.2", "recharts": "^3.7.0", diff --git a/src/components/PromoOffersSection.tsx b/src/components/PromoOffersSection.tsx index 6f7558f..00f22df 100644 --- a/src/components/PromoOffersSection.tsx +++ b/src/components/PromoOffersSection.tsx @@ -3,7 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; import { promoApi, PromoOffer } from '../api/promo'; -import { ClockIcon, CheckIcon } from './icons'; +import { ClockIcon, CheckIcon, XCircleIcon } from './icons'; import { useDestructiveConfirm } from '@/platform/hooks/useNativeDialog'; // Helper functions @@ -69,17 +69,6 @@ const getOfferDescription = ( return t('promo.offers.activateDiscountHint'); }; -// Icons for deactivation -const XCircleIcon = () => ( - - - -); - interface PromoOffersSectionProps { className?: string; } @@ -237,7 +226,7 @@ export default function PromoOffersSection({ className = '' }: PromoOffersSectio onClick={handleDeactivateClick} className="flex items-center justify-center gap-1.5 rounded-xl border border-dark-600/50 bg-dark-900/50 px-4 py-2.5 text-sm text-dark-400 transition-colors hover:border-error-500/30 hover:bg-error-500/10 hover:text-error-400" > - + {t('promo.deactivate.button')} diff --git a/src/components/SuccessNotificationModal.tsx b/src/components/SuccessNotificationModal.tsx index 72e78f2..4d656de 100644 --- a/src/components/SuccessNotificationModal.tsx +++ b/src/components/SuccessNotificationModal.tsx @@ -12,69 +12,14 @@ import { useCurrency } from '../hooks/useCurrency'; import { useTelegramSDK } from '../hooks/useTelegramSDK'; import { useFocusTrap } from '../hooks/useFocusTrap'; import { useHaptic } from '@/platform'; - -// Icons -const CheckCircleIcon = () => ( - - - -); - -const WalletIcon = () => ( - - - -); - -const RocketIcon = () => ( - - - -); - -const DevicesIcon = () => ( - - - -); - -const TrafficIcon = () => ( - - - -); - -const CloseIcon = () => ( - - - -); +import { + CheckCircleIcon, + CloseIcon, + DevicesIcon, + RocketIcon, + TrafficIcon, + WalletIcon, +} from '@/components/icons'; export default function SuccessNotificationModal() { const { t } = useTranslation(); @@ -161,33 +106,33 @@ export default function SuccessNotificationModal() { // Determine title and message let title = data.title; const message = data.message; - let icon = ; + let icon = ; let gradientClass = 'from-success-500 to-success-600'; if (!title) { if (isBalanceTopup) { title = t('successNotification.balanceTopup.title', 'Balance topped up!'); - icon = ; + icon = ; gradientClass = 'from-success-500 to-success-600'; } else if (data.type === 'subscription_activated') { title = t('successNotification.subscriptionActivated.title', 'Subscription activated!'); - icon = ; + icon = ; gradientClass = 'from-accent-500 to-purple-600'; } else if (data.type === 'subscription_renewed') { title = t('successNotification.subscriptionRenewed.title', 'Subscription renewed!'); - icon = ; + icon = ; gradientClass = 'from-accent-500 to-purple-600'; } else if (data.type === 'subscription_purchased') { title = t('successNotification.subscriptionPurchased.title', 'Subscription purchased!'); - icon = ; + icon = ; gradientClass = 'from-accent-500 to-purple-600'; } else if (data.type === 'devices_purchased') { title = t('successNotification.devicesPurchased.title', 'Devices added!'); - icon = ; + icon = ; gradientClass = 'from-blue-500 to-cyan-600'; } else if (data.type === 'traffic_purchased') { title = t('successNotification.trafficPurchased.title', 'Traffic added!'); - icon = ; + icon = ; gradientClass = 'from-success-500 to-success-600'; } } @@ -334,7 +279,7 @@ export default function SuccessNotificationModal() { onClick={handleGoToSubscription} className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600" > - + {t('successNotification.goToSubscription', 'Go to Subscription')} )} @@ -344,7 +289,7 @@ export default function SuccessNotificationModal() { onClick={handleGoToBalance} className="flex w-full items-center justify-center gap-2 rounded-xl bg-success-500 py-3.5 font-bold text-white shadow-lg shadow-success-500/25 transition-colors hover:bg-success-400 active:bg-success-600" > - + {t('successNotification.goToBalance', 'Go to Balance')} )} @@ -354,7 +299,7 @@ export default function SuccessNotificationModal() { onClick={handleGoToSubscription} className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600" > - + {t('successNotification.goToSubscription', 'Go to Subscription')} )} @@ -364,7 +309,7 @@ export default function SuccessNotificationModal() { onClick={handleGoToSubscription} className="flex w-full items-center justify-center gap-2 rounded-xl bg-success-500 py-3.5 font-bold text-white shadow-lg shadow-success-500/25 transition-colors hover:bg-success-400 active:bg-success-600" > - + {t('successNotification.goToSubscription', 'Go to Subscription')} )} diff --git a/src/components/TicketNotificationBell.tsx b/src/components/TicketNotificationBell.tsx index 88ef9b4..44b78f0 100644 --- a/src/components/TicketNotificationBell.tsx +++ b/src/components/TicketNotificationBell.tsx @@ -8,22 +8,7 @@ import { useToast } from './Toast'; import { useWebSocket, WSMessage } from '../hooks/useWebSocket'; import { useHeaderHeight } from '../hooks/useHeaderHeight'; import type { TicketNotification } from '../types'; - -const BellIcon = () => ( - - - -); - -const CheckIcon = () => ( - - - -); +import { BellIcon, CheckIcon } from '@/components/icons'; interface TicketNotificationBellProps { isAdmin?: boolean; diff --git a/src/components/admin/MenuEditorTab.tsx b/src/components/admin/MenuEditorTab.tsx index 80e532e..6fbd92f 100644 --- a/src/components/admin/MenuEditorTab.tsx +++ b/src/components/admin/MenuEditorTab.tsx @@ -18,6 +18,15 @@ import { verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; +import { PiCaretDownDuotone } from 'react-icons/pi'; +import { + GripIcon, + TrashIcon, + PlusIcon, + LinkIcon, + ArrowUpIcon, + ArrowDownIcon, +} from '@/components/icons'; import { menuLayoutApi, type MenuConfig, @@ -31,82 +40,10 @@ import { Toggle } from './Toggle'; import { useNotify } from '../../platform/hooks/useNotify'; import { useNativeDialog } from '../../platform/hooks/useNativeDialog'; -const GripIcon = () => ( - - - -); - -const TrashIcon = () => ( - - - -); - -const PlusIcon = () => ( - - - -); - const ChevronIcon = ({ expanded }: { expanded: boolean }) => ( - - - -); - -const LinkIcon = () => ( - - - -); - -const ArrowUpIcon = () => ( - - - -); - -const ArrowDownIcon = () => ( - - - + /> ); function generateId(): string { @@ -200,7 +137,7 @@ function ButtonChip({ : 'cursor-default text-dark-700' }`} > - + @@ -221,7 +158,7 @@ function ButtonChip({ {!isBuiltin && ( - + )} onUpdate({ enabled: !button.enabled })} /> @@ -236,7 +173,7 @@ function ButtonChip({ onClick={onRemove} className="rounded-lg p-1 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400" > - + )} @@ -425,7 +362,7 @@ function SortableRow({ onClick={() => onRemoveRow(row.id)} className="rounded-lg p-1.5 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400" > - + )} @@ -481,7 +418,7 @@ function InlineAddPanel({ rowId, usedBuiltinIds, onAddBuiltin, onAddCustom }: In onClick={() => setIsOpen(true)} className="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-dark-700/50 py-2.5 text-sm text-dark-500 transition-colors hover:border-dark-600 hover:text-dark-400" > - + {t('admin.menuEditor.addButton')} ); @@ -516,7 +453,7 @@ function InlineAddPanel({ rowId, usedBuiltinIds, onAddBuiltin, onAddCustom }: In }} className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-dark-200 transition-colors hover:bg-dark-700/50" > - + {t('admin.menuEditor.addUrlButton')} diff --git a/src/components/admin/SortableSelectedMethodCard.tsx b/src/components/admin/SortableSelectedMethodCard.tsx index e8063d8..a00ae63 100644 --- a/src/components/admin/SortableSelectedMethodCard.tsx +++ b/src/components/admin/SortableSelectedMethodCard.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSortable } from '@dnd-kit/sortable'; +import { PiCaretDownDuotone } from 'react-icons/pi'; import { CSS } from '@dnd-kit/utilities'; import { cn } from '../../lib/utils'; import { GripIcon, TrashIcon } from '../icons/LandingIcons'; @@ -10,15 +11,7 @@ import type { PaymentMethodSubOptionInfo } from '../../types'; export type MethodWithId = AdminLandingPaymentMethod & { _id: string }; const ChevronDownIcon = ({ open }: { open: boolean }) => ( - - - + ); interface SortableSelectedMethodProps { diff --git a/src/components/admin/bulkActions/ActionModal.tsx b/src/components/admin/bulkActions/ActionModal.tsx index e9d442d..8c14c51 100644 --- a/src/components/admin/bulkActions/ActionModal.tsx +++ b/src/components/admin/bulkActions/ActionModal.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { cn } from '@/lib/utils'; +import { CheckIcon, XCloseIcon } from '@/components/icons'; import { useFocusTrap } from '@/hooks/useFocusTrap'; import { DropdownSelect } from './DropdownSelect'; import type { UserListItem } from '../../../api/adminUsers'; @@ -48,24 +49,6 @@ export interface ModalState { progress: ProgressState | null; } -const CheckIcon = () => ( - - - -); - -const XCloseIcon = () => ( - - - -); - function ProgressView({ progress }: { progress: ProgressState }) { const { t } = useTranslation(); const logEndRef = useRef(null); @@ -520,7 +503,7 @@ export function ActionModal({ )} aria-pressed={forceDeleteActivePaid} > - {forceDeleteActivePaid && } + {forceDeleteActivePaid && } - {deleteFromPanel && } + {deleteFromPanel && } ( - - - -); +// Re-exported so the sibling FloatingActionBar / MultiSelectDropdown keep +// importing the chevron from this module while the glyph itself now comes +// from the central Phosphor barrel instead of a hand-written SVG. +export { ChevronDownIcon }; export interface DropdownOption { value: string; @@ -40,7 +40,7 @@ export function DropdownSelect({ value, options, onChange, className }: Dropdown ))}
- +
); diff --git a/src/components/admin/trafficUsage/TrafficIcons.tsx b/src/components/admin/trafficUsage/TrafficIcons.tsx index 78bf843..619b7ee 100644 --- a/src/components/admin/trafficUsage/TrafficIcons.tsx +++ b/src/components/admin/trafficUsage/TrafficIcons.tsx @@ -1,164 +1,35 @@ // ────────────────────────────────────────────────────────────────── -// TrafficIcons — the inline SVG set used across AdminTrafficUsage -// (header controls, filter buttons, sort indicator, etc.). Page-local -// in spirit; co-located here so the parent page imports a single -// flat barrel rather than redefining 15 inline icons. +// TrafficIcons — the icon set used across AdminTrafficUsage (header +// controls, filter buttons, sort indicator, etc.). The glyphs now come +// from the central Phosphor barrel (`@/components/icons`); only the +// sort indicator keeps its custom `direction` prop and is therefore a +// thin local wrapper over the panel's Phosphor caret icons. // ────────────────────────────────────────────────────────────────── -export const SearchIcon = () => ( - - - -); +import { PiCaretDownDuotone, PiCaretUpDownDuotone, PiCaretUpDuotone } from 'react-icons/pi'; -export const ChevronLeftIcon = () => ( - - - -); +export { + CalendarIcon, + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, + DownloadIcon, + FilterIcon, + GlobeIcon, + RefreshIcon, + SearchIcon, + ServerIcon, + ServerSmallIcon, + ShieldIcon, + StatusIcon, + XIcon, +} from '@/components/icons'; -export const ChevronRightIcon = () => ( - - - -); - -export const RefreshIcon = ({ className = 'w-5 h-5' }: { className?: string }) => ( - - - -); - -export const DownloadIcon = () => ( - - - -); - -export const SortIcon = ({ direction }: { direction: false | 'asc' | 'desc' }) => ( - - {direction === 'asc' ? ( - - ) : direction === 'desc' ? ( - - ) : ( - - )} - -); - -export const FilterIcon = () => ( - - - -); - -export const ChevronDownIcon = () => ( - - - -); - -export const ServerIcon = () => ( - - - -); - -export const CalendarIcon = () => ( - - - -); - -export const XIcon = () => ( - - - -); - -export const StatusIcon = () => ( - - - -); - -export const GlobeIcon = () => ( - - - -); - -export const ShieldIcon = () => ( - - - -); - -export const ServerSmallIcon = () => ( - - - -); +export const SortIcon = ({ direction }: { direction: false | 'asc' | 'desc' }) => + direction === 'asc' ? ( + + ) : direction === 'desc' ? ( + + ) : ( + + ); diff --git a/src/components/admin/trafficUsage/filters/CountryFilter.tsx b/src/components/admin/trafficUsage/filters/CountryFilter.tsx index 10977f7..391897a 100644 --- a/src/components/admin/trafficUsage/filters/CountryFilter.tsx +++ b/src/components/admin/trafficUsage/filters/CountryFilter.tsx @@ -49,13 +49,13 @@ export function CountryFilter({ : 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700' }`} > - + {activeCount > 0 && ( {activeCount} )} - + {open && ( diff --git a/src/components/admin/trafficUsage/filters/NodeFilter.tsx b/src/components/admin/trafficUsage/filters/NodeFilter.tsx index c3cb2b4..1a23e39 100644 --- a/src/components/admin/trafficUsage/filters/NodeFilter.tsx +++ b/src/components/admin/trafficUsage/filters/NodeFilter.tsx @@ -52,14 +52,14 @@ export function NodeFilter({ : 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700' }`} > - + {t('admin.trafficUsage.nodes')} {activeCount > 0 && ( {activeCount} )} - + {open && ( diff --git a/src/components/admin/trafficUsage/filters/PeriodSelector.tsx b/src/components/admin/trafficUsage/filters/PeriodSelector.tsx index 7b2c0c5..da00ca9 100644 --- a/src/components/admin/trafficUsage/filters/PeriodSelector.tsx +++ b/src/components/admin/trafficUsage/filters/PeriodSelector.tsx @@ -42,7 +42,7 @@ export function PeriodSelector({ if (dateMode) { return (
- + {t('admin.trafficUsage.dateFrom')} - +
); @@ -96,7 +96,7 @@ export function PeriodSelector({ className="rounded-lg border border-dark-700 bg-dark-800 p-1.5 text-dark-400 transition-colors hover:border-dark-600 hover:bg-dark-700 hover:text-dark-200" title={t('admin.trafficUsage.customDates')} > - + ); diff --git a/src/components/admin/trafficUsage/filters/StatusFilter.tsx b/src/components/admin/trafficUsage/filters/StatusFilter.tsx index f87df56..dc00641 100644 --- a/src/components/admin/trafficUsage/filters/StatusFilter.tsx +++ b/src/components/admin/trafficUsage/filters/StatusFilter.tsx @@ -63,14 +63,14 @@ export function StatusFilter({ : 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700' }`} > - + {t('admin.trafficUsage.status')} {activeCount > 0 && ( {activeCount} )} - + {open && ( diff --git a/src/components/admin/trafficUsage/filters/TariffFilter.tsx b/src/components/admin/trafficUsage/filters/TariffFilter.tsx index 5f4173c..ccdaaee 100644 --- a/src/components/admin/trafficUsage/filters/TariffFilter.tsx +++ b/src/components/admin/trafficUsage/filters/TariffFilter.tsx @@ -50,14 +50,14 @@ export function TariffFilter({ : 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700' }`} > - + {t('admin.trafficUsage.tariff')} {activeCount > 0 && ( {activeCount} )} - + {open && ( diff --git a/src/components/admin/userDetail/BalanceTab.tsx b/src/components/admin/userDetail/BalanceTab.tsx index af5d49a..c0538bd 100644 --- a/src/components/admin/userDetail/BalanceTab.tsx +++ b/src/components/admin/userDetail/BalanceTab.tsx @@ -6,22 +6,7 @@ import { adminUsersApi, type UserDetailResponse } from '../../../api/adminUsers' import { promocodesApi } from '../../../api/promocodes'; import { promoOffersApi } from '../../../api/promoOffers'; import { createNumberInputHandler, toNumber } from '../../../utils/inputHelpers'; - -// ────────────────────────────────────────────────────────────────── -// Icons — local; balance is the only consumer. -// ────────────────────────────────────────────────────────────────── - -const PlusIcon = () => ( - - - -); - -const MinusIcon = () => ( - - - -); +import { PlusIcon, MinusIcon } from '@/components/icons'; // ────────────────────────────────────────────────────────────────── // Balance tab — current balance, add/subtract form, active promo @@ -168,14 +153,14 @@ export function BalanceTab({ disabled={actionLoading || balanceAmount === ''} className="flex flex-1 items-center justify-center gap-2 rounded-lg bg-success-500 py-2 text-white transition-colors hover:bg-success-600 disabled:opacity-50" > - {t('admin.users.detail.balance.add')} + {t('admin.users.detail.balance.add')} diff --git a/src/components/admin/userDetail/SubscriptionTab.tsx b/src/components/admin/userDetail/SubscriptionTab.tsx index c45c52a..b382c1d 100644 --- a/src/components/admin/userDetail/SubscriptionTab.tsx +++ b/src/components/admin/userDetail/SubscriptionTab.tsx @@ -1,4 +1,5 @@ import { useTranslation } from 'react-i18next'; +import { MinusIcon, PlusIcon, RefreshIcon } from '@/components/icons'; import { DEVICE_ALIAS_MAX_LENGTH } from '../../../constants/devices'; import { createNumberInputHandler } from '../../../utils/inputHelpers'; import { getFlagEmoji } from '../../../utils/subscriptionHelpers'; @@ -37,28 +38,6 @@ function StatusBadge({ status }: { status: string }) { ); } -const PlusIcon = () => ( - - - -); - -const MinusIcon = () => ( - - - -); - -const RefreshIcon = ({ className = 'w-5 h-5' }: { className?: string }) => ( - - - -); - // Local device row type (matches the parent's inline type) type DeviceRow = { hwid: string; @@ -390,7 +369,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) { disabled={actionLoading || selectedSub.device_limit <= 1} className="flex h-6 w-6 items-center justify-center rounded-md bg-dark-700 text-dark-300 transition-colors hover:bg-dark-600 disabled:opacity-30" > - + {selectedSub.device_limit} @@ -404,7 +383,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) { } className="flex h-6 w-6 items-center justify-center rounded-md bg-dark-700 text-dark-300 transition-colors hover:bg-dark-600 disabled:opacity-30" > - + diff --git a/src/components/admin/userDetail/SyncTab.tsx b/src/components/admin/userDetail/SyncTab.tsx index 2721c1e..d88565d 100644 --- a/src/components/admin/userDetail/SyncTab.tsx +++ b/src/components/admin/userDetail/SyncTab.tsx @@ -1,26 +1,11 @@ import { useTranslation } from 'react-i18next'; +import { ArrowDownIcon, ArrowUpIcon } from '@/components/icons'; import type { UserDetailResponse, UserSubscriptionInfo, PanelSyncStatusResponse, } from '../../../api/adminUsers'; -// ────────────────────────────────────────────────────────────────── -// Icons (sync-tab-local — not used outside this view) -// ────────────────────────────────────────────────────────────────── - -const ArrowDownIcon = ({ className = 'w-5 h-5' }: { className?: string }) => ( - - - -); - -const ArrowUpIcon = ({ className = 'w-5 h-5' }: { className?: string }) => ( - - - -); - // ────────────────────────────────────────────────────────────────── // Sync tab — compares bot DB vs panel data, offers a 2-way push // ────────────────────────────────────────────────────────────────── diff --git a/src/components/connection/InstallationGuide.tsx b/src/components/connection/InstallationGuide.tsx index 055b761..73d4e19 100644 --- a/src/components/connection/InstallationGuide.tsx +++ b/src/components/connection/InstallationGuide.tsx @@ -12,6 +12,7 @@ import { useTheme } from '@/hooks/useTheme'; import { CardsBlock, TimelineBlock, AccordionBlock, MinimalBlock, BlockButtons } from './blocks'; import type { BlockRendererProps } from './blocks'; import TvQuickConnect from './TvQuickConnect'; +import { BackIcon } from '@/components/icons'; const platformOrder = ['ios', 'android', 'windows', 'macos', 'linux', 'androidTV', 'appleTV']; @@ -33,12 +34,6 @@ const RENDERERS: Record> = { minimal: MinimalBlock, }; -const BackIcon = () => ( - - - -); - interface Props { appConfig: AppConfig; onOpenDeepLink: (url: string) => void; @@ -200,7 +195,7 @@ export default function InstallationGuide({ aria-label={t('common.back', 'Back')} className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600" > - + )}

diff --git a/src/components/connection/blocks/BlockButtons.tsx b/src/components/connection/blocks/BlockButtons.tsx index dad4273..6f5341b 100644 --- a/src/components/connection/blocks/BlockButtons.tsx +++ b/src/components/connection/blocks/BlockButtons.tsx @@ -1,5 +1,6 @@ import { useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { CheckIcon, CopyIcon } from '@/components/icons'; import type { RemnawaveButtonClient, LocalizedText } from '@/types'; import { copyToClipboard } from '@/utils/clipboard'; @@ -20,22 +21,6 @@ function isValidExternalUrl(url: string | undefined): boolean { return lowerUrl.startsWith('http://') || lowerUrl.startsWith('https://'); } -const CopyIcon = () => ( - - - -); - -const CheckIcon = () => ( - - - -); - interface BlockButtonsProps { buttons: RemnawaveButtonClient[] | undefined; variant: 'light' | 'subtle'; diff --git a/src/components/dashboard/StatsGrid.tsx b/src/components/dashboard/StatsGrid.tsx index 791ffd1..6463ee9 100644 --- a/src/components/dashboard/StatsGrid.tsx +++ b/src/components/dashboard/StatsGrid.tsx @@ -1,3 +1,4 @@ +import { PiCaretRightDuotone } from 'react-icons/pi'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router'; import { useCurrency } from '../../hooks/useCurrency'; @@ -12,22 +13,7 @@ interface StatsGridProps { } const ChevronIcon = ({ color }: { color: string }) => ( - +