From 7f9ad0e977e303b351cbbdfd4f58195d7b6a32b6 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 9 Mar 2026 00:23:31 +0100 Subject: [PATCH] refactor: switch icons from react-icons to Phosphor Replace react-icons and @radix-ui/react-icons with @phosphor-icons/react for a consistent icon style across all dashboard panels. Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 1 + components/OfflineBanner.tsx | 4 +- components/dashboard/Campaigns.tsx | 4 +- components/dashboard/Locations.tsx | 9 +-- components/dashboard/TechSpecs.tsx | 6 +- components/tools/UtmBuilder.tsx | 4 +- lib/utils/icons.tsx | 126 +++++++++++++---------------- next.config.ts | 2 +- package-lock.json | 25 +++--- package.json | 3 +- 10 files changed, 84 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1242fbd..f303c7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Improved - **Cleaner site navigation.** Dashboard, Uptime, Funnels, and Settings now use an underline tab bar instead of floating buttons. The active section is highlighted with an orange underline, making it easy to see where you are and switch between views. +- **Consistent icon style.** All dashboard icons now use a single, unified icon set for a cleaner look across Technology, Locations, Campaigns, and Referrers panels. ### Fixed diff --git a/components/OfflineBanner.tsx b/components/OfflineBanner.tsx index bca7c20..784b8d9 100644 --- a/components/OfflineBanner.tsx +++ b/components/OfflineBanner.tsx @@ -1,13 +1,13 @@ 'use client'; -import { FiWifiOff } from 'react-icons/fi'; +import { WifiSlash } from '@phosphor-icons/react'; export function OfflineBanner({ isOnline }: { isOnline: boolean }) { if (isOnline) return null; return (
- + You are currently offline. Changes may not be saved.
); diff --git a/components/dashboard/Campaigns.tsx b/components/dashboard/Campaigns.tsx index defc79e..9f0ffe0 100644 --- a/components/dashboard/Campaigns.tsx +++ b/components/dashboard/Campaigns.tsx @@ -9,7 +9,7 @@ import { Modal, ArrowRightIcon } from '@ciphera-net/ui' import { ListSkeleton } from '@/components/skeletons' import { getCampaigns, CampaignStat } from '@/lib/api/stats' import { getReferrerFavicon, getReferrerIcon, getReferrerDisplayName } from '@/lib/utils/icons' -import { FaBullhorn } from 'react-icons/fa' +import { Megaphone } from '@phosphor-icons/react' import UtmBuilder from '@/components/tools/UtmBuilder' import { type DimensionFilter } from '@/lib/filters' @@ -190,7 +190,7 @@ export default function Campaigns({ siteId, dateRange, filters, onFilter }: Camp ) : (
- +

Track your marketing campaigns diff --git a/components/dashboard/Locations.tsx b/components/dashboard/Locations.tsx index 5cc15d4..74f819e 100644 --- a/components/dashboard/Locations.tsx +++ b/components/dashboard/Locations.tsx @@ -9,8 +9,7 @@ import iso3166 from 'iso-3166-2' import WorldMap from './WorldMap' import { Modal, GlobeIcon } from '@ciphera-net/ui' import { ListSkeleton } from '@/components/skeletons' -import { SiTorproject } from 'react-icons/si' -import { FaUserSecret, FaSatellite } from 'react-icons/fa' +import { ShieldCheck, Detective, Broadcast } from '@phosphor-icons/react' import { getCountries, getCities, getRegions } from '@/lib/api/stats' import { type DimensionFilter } from '@/lib/filters' @@ -69,11 +68,11 @@ export default function Locations({ countries, cities, regions, geoDataLevel = ' switch (countryCode) { case 'T1': - return + return case 'A1': - return + return case 'A2': - return + return case 'O1': case 'EU': case 'AP': diff --git a/components/dashboard/TechSpecs.tsx b/components/dashboard/TechSpecs.tsx index d31c27e..d9251ec 100644 --- a/components/dashboard/TechSpecs.tsx +++ b/components/dashboard/TechSpecs.tsx @@ -5,7 +5,7 @@ import { logger } from '@/lib/utils/logger' import { formatNumber } from '@ciphera-net/ui' import { useTabListKeyboard } from '@/lib/hooks/useTabListKeyboard' import { getBrowserIcon, getOSIcon, getDeviceIcon } from '@/lib/utils/icons' -import { MdMonitor } from 'react-icons/md' +import { Monitor } from '@phosphor-icons/react' import { Modal, GridIcon } from '@ciphera-net/ui' import { ListSkeleton } from '@/components/skeletons' import { getBrowsers, getOS, getDevices, getScreenResolutions } from '@/lib/api/stats' @@ -64,7 +64,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co data = res.map(d => ({ name: d.device, pageviews: d.pageviews, icon: getDeviceIcon(d.device) })) } else if (activeTab === 'screens') { const res = await getScreenResolutions(siteId, dateRange.start, dateRange.end, 100) - data = res.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews, icon: })) + data = res.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews, icon: })) } setFullData(filterUnknown(data)) } catch (e) { @@ -88,7 +88,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co case 'devices': return devices.map(d => ({ name: d.device, pageviews: d.pageviews, icon: getDeviceIcon(d.device) })) case 'screens': - return screenResolutions.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews, icon: })) + return screenResolutions.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews, icon: })) default: return [] } diff --git a/components/tools/UtmBuilder.tsx b/components/tools/UtmBuilder.tsx index 7f5ad34..8bced1b 100644 --- a/components/tools/UtmBuilder.tsx +++ b/components/tools/UtmBuilder.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { logger } from '@/lib/utils/logger' -import { CopyIcon, CheckIcon } from '@radix-ui/react-icons' +import { Copy, Check } from '@phosphor-icons/react' import { listSites, Site } from '@/lib/api/sites' import { Select, Input, Button } from '@ciphera-net/ui' @@ -205,7 +205,7 @@ export default function UtmBuilder({ initialSiteId }: UtmBuilderProps) { className="ml-4 shrink-0 h-9 w-9 p-0 rounded-lg" title="Copy to clipboard" > - {copied ? : } + {copied ? : }

)} diff --git a/lib/utils/icons.tsx b/lib/utils/icons.tsx index f3cca87..2b45fec 100644 --- a/lib/utils/icons.tsx +++ b/lib/utils/icons.tsx @@ -1,98 +1,80 @@ import React from 'react' +import { + Globe, + WindowsLogo, + AppleLogo, + LinuxLogo, + AndroidLogo, + Question, + DeviceMobile, + DeviceTablet, + Desktop, + GoogleLogo, + FacebookLogo, + XLogo, + LinkedinLogo, + InstagramLogo, + GithubLogo, + YoutubeLogo, + RedditLogo, + Robot, +} from '@phosphor-icons/react' /** * Google's public favicon service base URL. * Append `?domain=&sz=` to get a favicon. */ export const FAVICON_SERVICE_URL = 'https://www.google.com/s2/favicons' -import { - FaChrome, - FaFirefox, - FaSafari, - FaEdge, - FaOpera, - FaInternetExplorer, - FaWindows, - FaApple, - FaLinux, - FaAndroid, - FaDesktop, - FaMobileAlt, - FaTabletAlt, - FaGoogle, - FaFacebook, - FaLinkedin, - FaInstagram, - FaGithub, - FaYoutube, - FaReddit, - FaQuestion, - FaGlobe -} from 'react-icons/fa' -import { FaXTwitter } from 'react-icons/fa6' -import { SiBrave, SiOpenai, SiPerplexity, SiAnthropic, SiGooglegemini } from 'react-icons/si' -import { RiRobot2Fill } from 'react-icons/ri' -import { MdDeviceUnknown, MdSmartphone, MdTabletMac, MdDesktopWindows } from 'react-icons/md' export function getBrowserIcon(browserName: string) { - if (!browserName) return - const lower = browserName.toLowerCase() - if (lower.includes('chrome')) return - if (lower.includes('firefox')) return - if (lower.includes('safari')) return - if (lower.includes('edge')) return - if (lower.includes('opera')) return - if (lower.includes('ie') || lower.includes('explorer')) return - if (lower.includes('brave')) return - - return + if (!browserName) return + return } export function getOSIcon(osName: string) { - if (!osName) return + if (!osName) return const lower = osName.toLowerCase() - if (lower.includes('win')) return - if (lower.includes('mac') || lower.includes('ios')) return - if (lower.includes('linux') || lower.includes('ubuntu') || lower.includes('debian')) return - if (lower.includes('android')) return - - return + if (lower.includes('win')) return + if (lower.includes('mac') || lower.includes('ios')) return + if (lower.includes('linux') || lower.includes('ubuntu') || lower.includes('debian')) return + if (lower.includes('android')) return + + return } export function getDeviceIcon(deviceName: string) { - if (!deviceName) return + if (!deviceName) return const lower = deviceName.toLowerCase() - if (lower.includes('mobile') || lower.includes('phone')) return - if (lower.includes('tablet') || lower.includes('ipad')) return - if (lower.includes('desktop') || lower.includes('laptop')) return - - return + if (lower.includes('mobile') || lower.includes('phone')) return + if (lower.includes('tablet') || lower.includes('ipad')) return + if (lower.includes('desktop') || lower.includes('laptop')) return + + return } export function getReferrerIcon(referrerName: string) { - if (!referrerName) return + if (!referrerName) return const lower = referrerName.toLowerCase() - if (lower.includes('google')) return - if (lower.includes('facebook')) return - if (lower.includes('twitter') || lower.includes('t.co') || lower.includes('x.com')) return - if (lower.includes('linkedin')) return - if (lower.includes('instagram')) return - if (lower.includes('github')) return - if (lower.includes('youtube')) return - if (lower.includes('reddit')) return + if (lower.includes('google')) return + if (lower.includes('facebook')) return + if (lower.includes('twitter') || lower.includes('t.co') || lower.includes('x.com')) return + if (lower.includes('linkedin')) return + if (lower.includes('instagram')) return + if (lower.includes('github')) return + if (lower.includes('youtube')) return + if (lower.includes('reddit')) return // AI assistants and search tools - if (lower.includes('chatgpt') || lower.includes('openai')) return - if (lower.includes('perplexity')) return - if (lower.includes('claude') || lower.includes('anthropic')) return - if (lower.includes('gemini')) return - if (lower.includes('copilot')) return - if (lower.includes('deepseek')) return - if (lower.includes('grok') || lower.includes('x.ai')) return - if (lower.includes('phind')) return - if (lower.includes('you.com')) return + if (lower.includes('chatgpt') || lower.includes('openai')) return + if (lower.includes('perplexity')) return + if (lower.includes('claude') || lower.includes('anthropic')) return + if (lower.includes('gemini')) return + if (lower.includes('copilot')) return + if (lower.includes('deepseek')) return + if (lower.includes('grok') || lower.includes('x.ai')) return + if (lower.includes('phind')) return + if (lower.includes('you.com')) return - // Try to use a generic globe or maybe check if it is a URL - return + return } const REFERRER_NO_FAVICON = ['direct', 'unknown', ''] diff --git a/next.config.ts b/next.config.ts index 0052619..05f96a5 100644 --- a/next.config.ts +++ b/next.config.ts @@ -30,7 +30,7 @@ const nextConfig: NextConfig = { // * Privacy-first: Disable analytics and telemetry productionBrowserSourceMaps: false, experimental: { - optimizePackageImports: ['react-icons'], + optimizePackageImports: ['@phosphor-icons/react'], }, images: { remotePatterns: [ diff --git a/package-lock.json b/package-lock.json index fe86ed2..7c842cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@ciphera-net/ui": "^0.0.93", "@ducanh2912/next-pwa": "^10.2.9", - "@radix-ui/react-icons": "^1.3.0", + "@phosphor-icons/react": "^2.1.10", "@simplewebauthn/browser": "^13.2.2", "@stripe/react-stripe-js": "^5.6.0", "@stripe/stripe-js": "^8.7.0", @@ -26,7 +26,6 @@ "next": "^16.1.1", "react": "^19.2.3", "react-dom": "^19.2.3", - "react-icons": "^5.5.0", "react-markdown": "^10.1.0", "react-simple-maps": "^3.0.0", "recharts": "^2.15.0", @@ -3274,6 +3273,19 @@ "node": ">=12.4.0" } }, + "node_modules/@phosphor-icons/react": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz", + "integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">= 16.8", + "react-dom": ">= 16.8" + } + }, "node_modules/@radix-ui/react-icons": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", @@ -11081,15 +11093,6 @@ "react": "^19.2.4" } }, - "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": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 99d6af7..6cd541d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "@ciphera-net/ui": "^0.0.93", "@ducanh2912/next-pwa": "^10.2.9", - "@radix-ui/react-icons": "^1.3.0", + "@phosphor-icons/react": "^2.1.10", "@simplewebauthn/browser": "^13.2.2", "@stripe/react-stripe-js": "^5.6.0", "@stripe/stripe-js": "^8.7.0", @@ -30,7 +30,6 @@ "next": "^16.1.1", "react": "^19.2.3", "react-dom": "^19.2.3", - "react-icons": "^5.5.0", "react-markdown": "^10.1.0", "react-simple-maps": "^3.0.0", "recharts": "^2.15.0",