diff --git a/backend/routes/communitiesRoutes.js b/backend/routes/communitiesRoutes.js index f5793a6..5d52560 100644 --- a/backend/routes/communitiesRoutes.js +++ b/backend/routes/communitiesRoutes.js @@ -23,7 +23,6 @@ async function getCommunities(req, res) { name: c.name ? String(c.name) : '', description: c.description ? String(c.description) : '', tags: Array.isArray(c.tags) ? c.tags.map(String) : [], - gatewayDefault: c.gatewayDefault ? String(c.gatewayDefault) : '', color: c.color ? String(c.color) : '', icon: c.icon ? String(c.icon) : '' })); @@ -69,7 +68,6 @@ async function postCommunities(req, res) { name: entry.name ? String(entry.name) : '', description: entry.description ? String(entry.description) : '', tags: Array.isArray(entry.tags) ? entry.tags.map(String) : [], - gatewayDefault: entry.gatewayDefault ? String(entry.gatewayDefault) : '', color: entry.color ? String(entry.color) : '', icon: entry.icon ? String(entry.icon) : '', category: entry.category ? String(entry.category) : '', diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d809031..c4c139d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "diff": "^8.0.3", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^6.30.1", "recharts": "^3.7.0" }, @@ -2798,6 +2799,15 @@ "react": "^19.1.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "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/frontend/package.json b/frontend/package.json index a71e0f6..cde8868 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "diff": "^8.0.3", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^6.30.1", "recharts": "^3.7.0" }, diff --git a/frontend/src/CommunitiesManager.jsx b/frontend/src/CommunitiesManager.jsx index 581f5d6..4c180b2 100644 --- a/frontend/src/CommunitiesManager.jsx +++ b/frontend/src/CommunitiesManager.jsx @@ -54,9 +54,9 @@ function CommunitiesManager() { // Состояние для переключения между списком и статистикой const [activeTab, setActiveTab] = useState('list'); // 'list' | 'stats' - const [newItem, setNewItem] = useState({ value: '', name: '', description: '', tags: '', gatewayDefault: '', color: '', icon: '', category: '', priority: 0, enabled: true }); + const [newItem, setNewItem] = useState({ value: '', name: '', description: '', tags: '', color: '', icon: '', category: '', priority: 0, enabled: true }); const [editingValue, setEditingValue] = useState(null); - const [editingDraft, setEditingDraft] = useState({ value: '', name: '', description: '', tags: '', gatewayDefault: '', color: '', icon: '', category: '', priority: 0, enabled: true }); + const [editingDraft, setEditingDraft] = useState({ value: '', name: '', description: '', tags: '', color: '', icon: '', category: '', priority: 0, enabled: true }); const [filterUsage, setFilterUsage] = useState({}); const [filterUsageLoading, setFilterUsageLoading] = useState(false); const [filterUsageError, setFilterUsageError] = useState(''); @@ -80,7 +80,7 @@ function CommunitiesManager() { Object.keys(filterUsage).forEach((value) => { if (!value) return; if (!map.has(value)) { - map.set(value, { value, name: '', description: '', tags: [], gatewayDefault: '', color: '', icon: '', _external: true }); + map.set(value, { value, name: '', description: '', tags: [], color: '', icon: '', _external: true }); } }); return Array.from(map.values()); @@ -129,7 +129,6 @@ function CommunitiesManager() { name: String(d.name || ''), description: String(d.description || ''), tags: Array.isArray(d.tags) ? d.tags : [], - gatewayDefault: String(d.gatewayDefault || ''), color: String(d.color || ''), icon: String(d.icon || ''), _external: false, @@ -138,7 +137,7 @@ function CommunitiesManager() { // add unknown ones from usage for (const v of usedCommunities) { if (!map.has(v)) { - map.set(v, { value: v, name: '', description: '', tags: [], gatewayDefault: '', color: '', icon: '', _external: true }); + map.set(v, { value: v, name: '', description: '', tags: [], color: '', icon: '', _external: true }); } } setItems(Array.from(map.values())); @@ -217,11 +216,10 @@ function CommunitiesManager() { name: String(newItem.name || ''), description: String(newItem.description || ''), tags: toTagsArray(newItem.tags), - gatewayDefault: String(newItem.gatewayDefault || ''), color: String(newItem.color || ''), icon: String(newItem.icon || ''), }]); - setNewItem({ value: '', name: '', description: '', tags: '', gatewayDefault: '', color: '', icon: '' }); + setNewItem({ value: '', name: '', description: '', tags: '', color: '', icon: '' }); setAddModalOpen(false); setSuccess('Community успешно добавлен!'); setTimeout(() => setSuccess(''), 3000); @@ -234,7 +232,6 @@ function CommunitiesManager() { name: item.name || '', description: item.description || '', tags: fromTagsArray(item.tags), - gatewayDefault: item.gatewayDefault || '', color: item.color || '', icon: item.icon || '', }); @@ -251,7 +248,6 @@ function CommunitiesManager() { name: String(editingDraft.name || ''), description: String(editingDraft.description || ''), tags: toTagsArray(editingDraft.tags), - gatewayDefault: String(editingDraft.gatewayDefault || ''), color: String(editingDraft.color || ''), icon: String(editingDraft.icon || ''), } : i); @@ -283,7 +279,7 @@ function CommunitiesManager() { }; const handleImport = () => { - const text = window.prompt('Вставьте JSON-массив community (value, name, description, tags, gatewayDefault, color, icon)'); + const text = window.prompt('Вставьте JSON-массив community (value, name, description, tags, color, icon)'); if (!text) return; try { const arr = JSON.parse(text); @@ -300,7 +296,6 @@ function CommunitiesManager() { name: String(x.name || ''), description: String(x.description || ''), tags: Array.isArray(x.tags) ? x.tags : [], - gatewayDefault: String(x.gatewayDefault || ''), color: String(x.color || ''), icon: String(x.icon || ''), }); @@ -584,16 +579,15 @@ function CommunitiesManager() { Описание Теги - Gateway Исп. в фильтрах Действия {loading ? ( - Загрузка... + Загрузка... ) : paginated.length === 0 ? ( - + {iconInfo ? ( - - + + ) : ( @@ -634,9 +628,6 @@ function CommunitiesManager() { {t} ))} - - {item.gatewayDefault || '—'} - {usageCount > 0 ? ( {usageCount} в фильтрах @@ -691,7 +682,7 @@ function CommunitiesManager() { show={addModalOpen} onClose={() => { setAddModalOpen(false); - setNewItem({ value: '', name: '', description: '', tags: '', gatewayDefault: '', color: '', icon: '' }); + setNewItem({ value: '', name: '', description: '', tags: '', color: '', icon: '' }); setError(''); }} onSubmit={addItem} @@ -752,14 +743,6 @@ function CommunitiesManager() { helpText="Разделяйте теги запятыми" /> - setNewItem({...newItem, gatewayDefault: val})} - placeholder="SWE-HIPHOST" - /> - - setEditingDraft({...editingDraft, gatewayDefault: val})} - placeholder="SWE-HIPHOST" - /> - {open && ( -
+
@@ -81,7 +81,7 @@ export default function IconPicker({ value, onChange, disabled = false, label =
{filtered.map((item) => ( @@ -91,13 +91,13 @@ export default function IconPicker({ value, onChange, disabled = false, label = className={`btn btn-sm d-flex align-items-center justify-content-center rounded ${ value === item.id ? 'btn-primary' : 'btn-outline-secondary' }`} - style={{ width: 48, height: 48 }} + style={{ width: 56, height: 56 }} onClick={() => handleSelect(item)} title={item.label} role="option" aria-selected={value === item.id} > - + ))}
diff --git a/frontend/src/components/filter/AddFilterModal.jsx b/frontend/src/components/filter/AddFilterModal.jsx index 7d85462..19f2451 100644 --- a/frontend/src/components/filter/AddFilterModal.jsx +++ b/frontend/src/components/filter/AddFilterModal.jsx @@ -42,10 +42,6 @@ function AddFilterModal({ show, newFilter, onNewFilterChange, onAddFilter, onClo onChange={(e) => { const v = e.target.value; onNewFilterChange({ ...newFilter, community: v }); - const found = communities.find(c => c.value === v); - if (found && found.gatewayDefault && !newFilter.gateway) { - onNewFilterChange({ ...newFilter, community: v, gateway: found.gatewayDefault }); - } }} required /> diff --git a/frontend/src/lib/brandIcons.js b/frontend/src/lib/brandIcons.js index 474345b..30579a9 100644 --- a/frontend/src/lib/brandIcons.js +++ b/frontend/src/lib/brandIcons.js @@ -1,6 +1,6 @@ /** - * Реестр брендовых иконок (Tabler Icons) для выбора в настройках пинг-сервисов. - * id — имя компонента (например IconBrandGoogle), label — для поиска и отображения. + * Реестр брендовых иконок для выбора в UI. + * Используем Tabler + Font Awesome + Bootstrap Icons через react-icons. */ import { IconBrandGoogle, @@ -39,44 +39,130 @@ import { IconWorld, IconCloud, } from '@tabler/icons-react'; +import { + FaSteam, + FaSteamSymbol, + FaCloudflare, + FaDiscord, + FaGithub, + FaTelegram, + FaVk, + FaYoutube, + FaGoogle, + FaWhatsapp, + FaApple, + FaAmazon, + FaFacebook, + FaXTwitter, + FaTiktok, + FaLinkedin, + FaReddit, + FaPinterest, + FaChrome, + FaFirefox, + FaEdge, + FaSafari, + FaDocker, + FaSlack, + FaCcVisa, + FaCcMastercard, + FaCcPaypal, + FaStripe, + FaYandex, +} from 'react-icons/fa6'; +import { + BsTelegram, + BsDiscord, + BsSteam, + BsCloud, + BsGlobe, + BsGithub, + BsGoogle, + BsYoutube, + BsTwitterX, + BsWhatsapp, + BsApple, + BsTiktok, +} from 'react-icons/bs'; const BRAND_ICONS = [ - { id: 'IconBrandGoogle', label: 'Google', Icon: IconBrandGoogle }, - { id: 'IconBrandCloudflare', label: 'Cloudflare', Icon: IconBrandCloudflare }, + { id: 'IconBrandGoogle', label: 'Google (Tabler)', Icon: IconBrandGoogle }, + { id: 'IconBrandCloudflare', label: 'Cloudflare (Tabler)', Icon: IconBrandCloudflare }, { id: 'IconCdnBunny', label: 'Bunny CDN', Icon: IconCloud }, { id: 'IconCdnFastly', label: 'Fastly CDN', Icon: IconCloud }, - { id: 'IconBrandYandex', label: 'Yandex', Icon: IconBrandYandex }, - { id: 'IconBrandInstagram', label: 'Instagram', Icon: IconBrandInstagram }, - { id: 'IconBrandGithub', label: 'GitHub', Icon: IconBrandGithub }, - { id: 'IconBrandTelegram', label: 'Telegram', Icon: IconBrandTelegram }, - { id: 'IconBrandWhatsapp', label: 'WhatsApp', Icon: IconBrandWhatsapp }, - { id: 'IconBrandFacebook', label: 'Facebook', Icon: IconBrandFacebook }, - { id: 'IconBrandX', label: 'X (Twitter)', Icon: IconBrandX }, - { id: 'IconBrandYoutube', label: 'YouTube', Icon: IconBrandYoutube }, - { id: 'IconBrandTiktok', label: 'TikTok', Icon: IconBrandTiktok }, - { id: 'IconBrandAmazon', label: 'Amazon', Icon: IconBrandAmazon }, - { id: 'IconBrandApple', label: 'Apple', Icon: IconBrandApple }, - { id: 'IconBrandDiscord', label: 'Discord', Icon: IconBrandDiscord }, - { id: 'IconBrandSpotify', label: 'Spotify', Icon: IconBrandSpotify }, - { id: 'IconBrandVk', label: 'VK', Icon: IconBrandVk }, - { id: 'IconBrandReddit', label: 'Reddit', Icon: IconBrandReddit }, - { id: 'IconBrandLinkedin', label: 'LinkedIn', Icon: IconBrandLinkedin }, - { id: 'IconBrandPinterest', label: 'Pinterest', Icon: IconBrandPinterest }, - { id: 'IconBrandTwitch', label: 'Twitch', Icon: IconBrandTwitch }, - { id: 'IconBrandSteam', label: 'Steam', Icon: IconBrandSteam }, - { id: 'IconBrandDocker', label: 'Docker', Icon: IconBrandDocker }, - { id: 'IconBrandChrome', label: 'Chrome', Icon: IconBrandChrome }, - { id: 'IconBrandFirefox', label: 'Firefox', Icon: IconBrandFirefox }, - { id: 'IconBrandSafari', label: 'Safari', Icon: IconBrandSafari }, - { id: 'IconBrandEdge', label: 'Edge', Icon: IconBrandEdge }, - { id: 'IconBrandSlack', label: 'Slack', Icon: IconBrandSlack }, - { id: 'IconBrandZoom', label: 'Zoom', Icon: IconBrandZoom }, - { id: 'IconBrandSkype', label: 'Skype', Icon: IconBrandSkype }, - { id: 'IconBrandPaypal', label: 'PayPal', Icon: IconBrandPaypal }, - { id: 'IconBrandStripe', label: 'Stripe', Icon: IconBrandStripe }, - { id: 'IconBrandVisa', label: 'Visa', Icon: IconBrandVisa }, - { id: 'IconBrandMastercard', label: 'Mastercard', Icon: IconBrandMastercard }, - { id: 'IconWorld', label: 'Сеть (World)', Icon: IconWorld }, + { id: 'IconBrandYandex', label: 'Yandex (Tabler)', Icon: IconBrandYandex }, + { id: 'IconBrandInstagram', label: 'Instagram (Tabler)', Icon: IconBrandInstagram }, + { id: 'IconBrandGithub', label: 'GitHub (Tabler)', Icon: IconBrandGithub }, + { id: 'IconBrandTelegram', label: 'Telegram (Tabler)', Icon: IconBrandTelegram }, + { id: 'IconBrandWhatsapp', label: 'WhatsApp (Tabler)', Icon: IconBrandWhatsapp }, + { id: 'IconBrandFacebook', label: 'Facebook (Tabler)', Icon: IconBrandFacebook }, + { id: 'IconBrandX', label: 'X / Twitter (Tabler)', Icon: IconBrandX }, + { id: 'IconBrandYoutube', label: 'YouTube (Tabler)', Icon: IconBrandYoutube }, + { id: 'IconBrandTiktok', label: 'TikTok (Tabler)', Icon: IconBrandTiktok }, + { id: 'IconBrandAmazon', label: 'Amazon (Tabler)', Icon: IconBrandAmazon }, + { id: 'IconBrandApple', label: 'Apple (Tabler)', Icon: IconBrandApple }, + { id: 'IconBrandDiscord', label: 'Discord (Tabler)', Icon: IconBrandDiscord }, + { id: 'IconBrandSpotify', label: 'Spotify (Tabler)', Icon: IconBrandSpotify }, + { id: 'IconBrandVk', label: 'VK (Tabler)', Icon: IconBrandVk }, + { id: 'IconBrandReddit', label: 'Reddit (Tabler)', Icon: IconBrandReddit }, + { id: 'IconBrandLinkedin', label: 'LinkedIn (Tabler)', Icon: IconBrandLinkedin }, + { id: 'IconBrandPinterest', label: 'Pinterest (Tabler)', Icon: IconBrandPinterest }, + { id: 'IconBrandTwitch', label: 'Twitch (Tabler)', Icon: IconBrandTwitch }, + { id: 'IconBrandSteam', label: 'Steam (Tabler)', Icon: IconBrandSteam }, + { id: 'IconBrandDocker', label: 'Docker (Tabler)', Icon: IconBrandDocker }, + { id: 'IconBrandChrome', label: 'Chrome (Tabler)', Icon: IconBrandChrome }, + { id: 'IconBrandFirefox', label: 'Firefox (Tabler)', Icon: IconBrandFirefox }, + { id: 'IconBrandSafari', label: 'Safari (Tabler)', Icon: IconBrandSafari }, + { id: 'IconBrandEdge', label: 'Edge (Tabler)', Icon: IconBrandEdge }, + { id: 'IconBrandSlack', label: 'Slack (Tabler)', Icon: IconBrandSlack }, + { id: 'IconBrandZoom', label: 'Zoom (Tabler)', Icon: IconBrandZoom }, + { id: 'IconBrandSkype', label: 'Skype (Tabler)', Icon: IconBrandSkype }, + { id: 'IconBrandPaypal', label: 'PayPal (Tabler)', Icon: IconBrandPaypal }, + { id: 'IconBrandStripe', label: 'Stripe (Tabler)', Icon: IconBrandStripe }, + { id: 'IconBrandVisa', label: 'Visa (Tabler)', Icon: IconBrandVisa }, + { id: 'IconBrandMastercard', label: 'Mastercard (Tabler)', Icon: IconBrandMastercard }, + { id: 'IconWorld', label: 'Сеть (Tabler)', Icon: IconWorld }, + { id: 'FaSteam', label: 'Steam (Font Awesome)', Icon: FaSteam }, + { id: 'FaSteamSymbol', label: 'Valve / Steam (Font Awesome)', Icon: FaSteamSymbol }, + { id: 'FaCloudflare', label: 'Cloudflare (Font Awesome)', Icon: FaCloudflare }, + { id: 'FaDiscord', label: 'Discord (Font Awesome)', Icon: FaDiscord }, + { id: 'FaGithub', label: 'GitHub (Font Awesome)', Icon: FaGithub }, + { id: 'FaTelegram', label: 'Telegram (Font Awesome)', Icon: FaTelegram }, + { id: 'FaVk', label: 'VK (Font Awesome)', Icon: FaVk }, + { id: 'FaYoutube', label: 'YouTube (Font Awesome)', Icon: FaYoutube }, + { id: 'FaGoogle', label: 'Google (Font Awesome)', Icon: FaGoogle }, + { id: 'FaWhatsapp', label: 'WhatsApp (Font Awesome)', Icon: FaWhatsapp }, + { id: 'FaApple', label: 'Apple (Font Awesome)', Icon: FaApple }, + { id: 'FaAmazon', label: 'Amazon (Font Awesome)', Icon: FaAmazon }, + { id: 'FaFacebook', label: 'Facebook (Font Awesome)', Icon: FaFacebook }, + { id: 'FaXTwitter', label: 'X / Twitter (Font Awesome)', Icon: FaXTwitter }, + { id: 'FaTiktok', label: 'TikTok (Font Awesome)', Icon: FaTiktok }, + { id: 'FaLinkedin', label: 'LinkedIn (Font Awesome)', Icon: FaLinkedin }, + { id: 'FaReddit', label: 'Reddit (Font Awesome)', Icon: FaReddit }, + { id: 'FaPinterest', label: 'Pinterest (Font Awesome)', Icon: FaPinterest }, + { id: 'FaChrome', label: 'Chrome (Font Awesome)', Icon: FaChrome }, + { id: 'FaFirefox', label: 'Firefox (Font Awesome)', Icon: FaFirefox }, + { id: 'FaEdge', label: 'Edge (Font Awesome)', Icon: FaEdge }, + { id: 'FaSafari', label: 'Safari (Font Awesome)', Icon: FaSafari }, + { id: 'FaDocker', label: 'Docker (Font Awesome)', Icon: FaDocker }, + { id: 'FaSlack', label: 'Slack (Font Awesome)', Icon: FaSlack }, + { id: 'FaCcVisa', label: 'Visa (Font Awesome)', Icon: FaCcVisa }, + { id: 'FaCcMastercard', label: 'Mastercard (Font Awesome)', Icon: FaCcMastercard }, + { id: 'FaCcPaypal', label: 'PayPal (Font Awesome)', Icon: FaCcPaypal }, + { id: 'FaStripe', label: 'Stripe (Font Awesome)', Icon: FaStripe }, + { id: 'FaYandex', label: 'Yandex (Font Awesome)', Icon: FaYandex }, + { id: 'BsTelegram', label: 'Telegram (Bootstrap Icons)', Icon: BsTelegram }, + { id: 'BsDiscord', label: 'Discord (Bootstrap Icons)', Icon: BsDiscord }, + { id: 'BsSteam', label: 'Steam (Bootstrap Icons)', Icon: BsSteam }, + { id: 'BsCloud', label: 'Cloud (Bootstrap Icons)', Icon: BsCloud }, + { id: 'BsGlobe', label: 'Globe (Bootstrap Icons)', Icon: BsGlobe }, + { id: 'BsGithub', label: 'GitHub (Bootstrap Icons)', Icon: BsGithub }, + { id: 'BsGoogle', label: 'Google (Bootstrap Icons)', Icon: BsGoogle }, + { id: 'BsYoutube', label: 'YouTube (Bootstrap Icons)', Icon: BsYoutube }, + { id: 'BsTwitterX', label: 'X / Twitter (Bootstrap Icons)', Icon: BsTwitterX }, + { id: 'BsWhatsapp', label: 'WhatsApp (Bootstrap Icons)', Icon: BsWhatsapp }, + { id: 'BsApple', label: 'Apple (Bootstrap Icons)', Icon: BsApple }, + { id: 'BsTiktok', label: 'TikTok (Bootstrap Icons)', Icon: BsTiktok }, ]; const ICON_BY_ID = Object.fromEntries(BRAND_ICONS.map((item) => [item.id, item])); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 57b92ea..099ed03 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -57,7 +57,6 @@ export interface Community { value: string; name?: string; description?: string; - gatewayDefault?: string; icon?: string; category?: string; }