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",