From 42492d64b97c75ca0c34e058060b499a340de807 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Fri, 16 Jan 2026 23:39:10 +0100 Subject: [PATCH] feat(analytics): add icons for browsers, devices, OS, and referrers --- components/dashboard/TechSpecs.tsx | 13 ++-- components/dashboard/TopReferrers.tsx | 10 +-- lib/utils/icons.tsx | 88 +++++++++++++++++++++++++++ package-lock.json | 10 +++ package.json | 1 + 5 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 lib/utils/icons.tsx diff --git a/components/dashboard/TechSpecs.tsx b/components/dashboard/TechSpecs.tsx index e194a85..d33f1f8 100644 --- a/components/dashboard/TechSpecs.tsx +++ b/components/dashboard/TechSpecs.tsx @@ -2,6 +2,8 @@ import { useState } from 'react' import { formatNumber } from '@/lib/utils/format' +import { getBrowserIcon, getOSIcon, getDeviceIcon } from '@/lib/utils/icons' +import { MdMonitor } from 'react-icons/md' interface TechSpecsProps { browsers: Array<{ browser: string; pageviews: number }> @@ -16,16 +18,16 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions }: const [activeTab, setActiveTab] = useState('browsers') const renderContent = () => { - let data: Array<{ name: string; pageviews: number }> = [] + let data: Array<{ name: string; pageviews: number; icon?: React.ReactNode }> = [] if (activeTab === 'browsers') { - data = browsers.map(b => ({ name: b.browser, pageviews: b.pageviews })) + data = browsers.map(b => ({ name: b.browser, pageviews: b.pageviews, icon: getBrowserIcon(b.browser) })) } else if (activeTab === 'os') { - data = os.map(o => ({ name: o.os, pageviews: o.pageviews })) + data = os.map(o => ({ name: o.os, pageviews: o.pageviews, icon: getOSIcon(o.os) })) } else if (activeTab === 'devices') { - data = devices.map(d => ({ name: d.device, pageviews: d.pageviews })) + data = devices.map(d => ({ name: d.device, pageviews: d.pageviews, icon: getDeviceIcon(d.device) })) } else if (activeTab === 'screens') { - data = screenResolutions.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews })) + data = screenResolutions.map(s => ({ name: s.screen_resolution, pageviews: s.pageviews, icon: })) } if (!data || data.length === 0) { @@ -37,6 +39,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions }: {data.map((item, index) => (
+ {item.icon && {item.icon}} {item.name === 'Unknown' ? 'Unknown' : item.name}
diff --git a/components/dashboard/TopReferrers.tsx b/components/dashboard/TopReferrers.tsx index 49deade..e533c08 100644 --- a/components/dashboard/TopReferrers.tsx +++ b/components/dashboard/TopReferrers.tsx @@ -1,6 +1,7 @@ 'use client' import { formatNumber } from '@/lib/utils/format' +import { getReferrerIcon } from '@/lib/utils/icons' interface TopReferrersProps { referrers: Array<{ referrer: string; pageviews: number }> @@ -9,7 +10,7 @@ interface TopReferrersProps { export default function TopReferrers({ referrers }: TopReferrersProps) { if (!referrers || referrers.length === 0) { return ( -
+

Top Referrers

@@ -19,15 +20,16 @@ export default function TopReferrers({ referrers }: TopReferrersProps) { } return ( -
+

Top Referrers

{referrers.map((ref, index) => (
-
- {ref.referrer} +
+ {getReferrerIcon(ref.referrer)} + {ref.referrer}
{formatNumber(ref.pageviews)} diff --git a/lib/utils/icons.tsx b/lib/utils/icons.tsx new file mode 100644 index 0000000..078b6f2 --- /dev/null +++ b/lib/utils/icons.tsx @@ -0,0 +1,88 @@ +import React from 'react' +import { + FaChrome, + FaFirefox, + FaSafari, + FaEdge, + FaOpera, + FaInternetExplorer, + FaWindows, + FaApple, + FaLinux, + FaAndroid, + FaDesktop, + FaMobileAlt, + FaTabletAlt, + FaGoogle, + FaFacebook, + FaTwitter, + FaLinkedin, + FaInstagram, + FaGithub, + FaYoutube, + FaReddit, + FaQuestion, + FaGlobe +} from 'react-icons/fa' +import { SiBrave } from 'react-icons/si' +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 +} + +export function getOSIcon(osName: string) { + 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 +} + +export function getDeviceIcon(deviceName: string) { + 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 +} + +export function getReferrerIcon(referrerName: string) { + 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 + + // Try to use a generic globe or maybe check if it is a URL + return +} + +export function getReferrerFavicon(referrer: string) { + try { + const url = new URL(referrer.startsWith('http') ? referrer : `https://${referrer}`); + return `https://www.google.com/s2/favicons?domain=${url.hostname}&sz=32`; + } catch (e) { + return null; + } +} diff --git a/package-lock.json b/package-lock.json index 1f938af..8d0c2f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "next-themes": "^0.4.6", "react": "^19.2.3", "react-dom": "^19.2.3", + "react-icons": "^5.5.0", "react-simple-maps": "^3.0.0", "recharts": "^2.15.0", "sonner": "^2.0.7" @@ -5910,6 +5911,15 @@ "react": "^19.2.3" } }, + "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 234da96..dabc52f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "next-themes": "^0.4.6", "react": "^19.2.3", "react-dom": "^19.2.3", + "react-icons": "^5.5.0", "react-simple-maps": "^3.0.0", "recharts": "^2.15.0", "sonner": "^2.0.7"