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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<div className="fixed top-0 left-0 right-0 z-[100] rounded-b-xl bg-yellow-500/15 dark:bg-yellow-500/25 border-b border-yellow-500/30 dark:border-yellow-500/40 text-yellow-700 dark:text-yellow-300 px-4 sm:px-8 py-2.5 text-sm flex items-center justify-center gap-2 font-medium shadow-md transition-shadow duration-300">
|
||||
<FiWifiOff className="w-4 h-4 shrink-0" />
|
||||
<WifiSlash className="w-4 h-4 shrink-0" />
|
||||
<span>You are currently offline. Changes may not be saved.</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
) : (
|
||||
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
||||
<div className="rounded-full bg-neutral-100 dark:bg-neutral-800 p-4">
|
||||
<FaBullhorn className="w-8 h-8 text-neutral-500 dark:text-neutral-400" />
|
||||
<Megaphone className="w-8 h-8 text-neutral-500 dark:text-neutral-400" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-neutral-900 dark:text-white">
|
||||
Track your marketing campaigns
|
||||
|
||||
@@ -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 <SiTorproject className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
return <ShieldCheck className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
case 'A1':
|
||||
return <FaUserSecret className="w-5 h-5 text-neutral-600 dark:text-neutral-400" />
|
||||
return <Detective className="w-5 h-5 text-neutral-600 dark:text-neutral-400" />
|
||||
case 'A2':
|
||||
return <FaSatellite className="w-5 h-5 text-blue-500 dark:text-blue-400" />
|
||||
return <Broadcast className="w-5 h-5 text-blue-500 dark:text-blue-400" />
|
||||
case 'O1':
|
||||
case 'EU':
|
||||
case 'AP':
|
||||
|
||||
@@ -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: <MdMonitor className="text-neutral-500" /> }))
|
||||
data = res.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews, icon: <Monitor className="text-neutral-500" /> }))
|
||||
}
|
||||
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: <MdMonitor className="text-neutral-500" /> }))
|
||||
return screenResolutions.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews, icon: <Monitor className="text-neutral-500" /> }))
|
||||
default:
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -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 ? <CheckIcon className="w-4 h-4 text-green-500" /> : <CopyIcon className="w-4 h-4" />}
|
||||
{copied ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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=<host>&sz=<px>` 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 <FaGlobe className="text-neutral-400" />
|
||||
const lower = browserName.toLowerCase()
|
||||
if (lower.includes('chrome')) return <FaChrome className="text-blue-500" />
|
||||
if (lower.includes('firefox')) return <FaFirefox className="text-orange-500" />
|
||||
if (lower.includes('safari')) return <FaSafari className="text-blue-400" />
|
||||
if (lower.includes('edge')) return <FaEdge className="text-blue-600" />
|
||||
if (lower.includes('opera')) return <FaOpera className="text-red-500" />
|
||||
if (lower.includes('ie') || lower.includes('explorer')) return <FaInternetExplorer className="text-blue-500" />
|
||||
if (lower.includes('brave')) return <SiBrave className="text-orange-600" />
|
||||
|
||||
return <FaGlobe className="text-neutral-400" />
|
||||
if (!browserName) return <Globe className="text-neutral-400" />
|
||||
return <Globe className="text-neutral-500" />
|
||||
}
|
||||
|
||||
export function getOSIcon(osName: string) {
|
||||
if (!osName) return <MdDeviceUnknown className="text-neutral-400" />
|
||||
if (!osName) return <Question className="text-neutral-400" />
|
||||
const lower = osName.toLowerCase()
|
||||
if (lower.includes('win')) return <FaWindows className="text-blue-500" />
|
||||
if (lower.includes('mac') || lower.includes('ios')) return <FaApple className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('linux') || lower.includes('ubuntu') || lower.includes('debian')) return <FaLinux className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('android')) return <FaAndroid className="text-green-500" />
|
||||
if (lower.includes('win')) return <WindowsLogo className="text-blue-500" />
|
||||
if (lower.includes('mac') || lower.includes('ios')) return <AppleLogo className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('linux') || lower.includes('ubuntu') || lower.includes('debian')) return <LinuxLogo className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('android')) return <AndroidLogo className="text-green-500" />
|
||||
|
||||
return <MdDeviceUnknown className="text-neutral-400" />
|
||||
return <Question className="text-neutral-400" />
|
||||
}
|
||||
|
||||
export function getDeviceIcon(deviceName: string) {
|
||||
if (!deviceName) return <MdDeviceUnknown className="text-neutral-400" />
|
||||
if (!deviceName) return <Question className="text-neutral-400" />
|
||||
const lower = deviceName.toLowerCase()
|
||||
if (lower.includes('mobile') || lower.includes('phone')) return <MdSmartphone className="text-neutral-500" />
|
||||
if (lower.includes('tablet') || lower.includes('ipad')) return <MdTabletMac className="text-neutral-500" />
|
||||
if (lower.includes('desktop') || lower.includes('laptop')) return <MdDesktopWindows className="text-neutral-500" />
|
||||
if (lower.includes('mobile') || lower.includes('phone')) return <DeviceMobile className="text-neutral-500" />
|
||||
if (lower.includes('tablet') || lower.includes('ipad')) return <DeviceTablet className="text-neutral-500" />
|
||||
if (lower.includes('desktop') || lower.includes('laptop')) return <Desktop className="text-neutral-500" />
|
||||
|
||||
return <MdDeviceUnknown className="text-neutral-400" />
|
||||
return <Question className="text-neutral-400" />
|
||||
}
|
||||
|
||||
export function getReferrerIcon(referrerName: string) {
|
||||
if (!referrerName) return <FaGlobe className="text-neutral-400" />
|
||||
if (!referrerName) return <Globe className="text-neutral-400" />
|
||||
const lower = referrerName.toLowerCase()
|
||||
if (lower.includes('google')) return <FaGoogle className="text-blue-500" />
|
||||
if (lower.includes('facebook')) return <FaFacebook className="text-blue-600" />
|
||||
if (lower.includes('twitter') || lower.includes('t.co') || lower.includes('x.com')) return <FaXTwitter className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('linkedin')) return <FaLinkedin className="text-blue-700" />
|
||||
if (lower.includes('instagram')) return <FaInstagram className="text-pink-600" />
|
||||
if (lower.includes('github')) return <FaGithub className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('youtube')) return <FaYoutube className="text-red-600" />
|
||||
if (lower.includes('reddit')) return <FaReddit className="text-orange-600" />
|
||||
if (lower.includes('google')) return <GoogleLogo className="text-blue-500" />
|
||||
if (lower.includes('facebook')) return <FacebookLogo className="text-blue-600" />
|
||||
if (lower.includes('twitter') || lower.includes('t.co') || lower.includes('x.com')) return <XLogo className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('linkedin')) return <LinkedinLogo className="text-blue-700" />
|
||||
if (lower.includes('instagram')) return <InstagramLogo className="text-pink-600" />
|
||||
if (lower.includes('github')) return <GithubLogo className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('youtube')) return <YoutubeLogo className="text-red-600" />
|
||||
if (lower.includes('reddit')) return <RedditLogo className="text-orange-600" />
|
||||
// AI assistants and search tools
|
||||
if (lower.includes('chatgpt') || lower.includes('openai')) return <SiOpenai className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('perplexity')) return <SiPerplexity className="text-teal-600" />
|
||||
if (lower.includes('claude') || lower.includes('anthropic')) return <SiAnthropic className="text-orange-500" />
|
||||
if (lower.includes('gemini')) return <SiGooglegemini className="text-blue-500" />
|
||||
if (lower.includes('copilot')) return <FaGlobe className="text-blue-500" />
|
||||
if (lower.includes('deepseek')) return <RiRobot2Fill className="text-blue-600" />
|
||||
if (lower.includes('grok') || lower.includes('x.ai')) return <FaXTwitter className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('phind')) return <RiRobot2Fill className="text-purple-600" />
|
||||
if (lower.includes('you.com')) return <RiRobot2Fill className="text-indigo-600" />
|
||||
if (lower.includes('chatgpt') || lower.includes('openai')) return <Robot className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('perplexity')) return <Robot className="text-teal-600" />
|
||||
if (lower.includes('claude') || lower.includes('anthropic')) return <Robot className="text-orange-500" />
|
||||
if (lower.includes('gemini')) return <Robot className="text-blue-500" />
|
||||
if (lower.includes('copilot')) return <Robot className="text-blue-500" />
|
||||
if (lower.includes('deepseek')) return <Robot className="text-blue-600" />
|
||||
if (lower.includes('grok') || lower.includes('x.ai')) return <XLogo className="text-neutral-800 dark:text-neutral-200" />
|
||||
if (lower.includes('phind')) return <Robot className="text-purple-600" />
|
||||
if (lower.includes('you.com')) return <Robot className="text-indigo-600" />
|
||||
|
||||
// Try to use a generic globe or maybe check if it is a URL
|
||||
return <FaGlobe className="text-neutral-400" />
|
||||
return <Globe className="text-neutral-400" />
|
||||
}
|
||||
|
||||
const REFERRER_NO_FAVICON = ['direct', 'unknown', '']
|
||||
|
||||
@@ -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: [
|
||||
|
||||
25
package-lock.json
generated
25
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user