feat(analytics): add icons for browsers, devices, OS, and referrers
This commit is contained in:
@@ -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<Tab>('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: <MdMonitor className="text-neutral-500" /> }))
|
||||
}
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
@@ -37,6 +39,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions }:
|
||||
{data.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<div className="flex-1 truncate text-neutral-900 dark:text-white flex items-center gap-3">
|
||||
{item.icon && <span className="text-lg">{item.icon}</span>}
|
||||
<span className="truncate">{item.name === 'Unknown' ? 'Unknown' : item.name}</span>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
|
||||
@@ -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 (
|
||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6">
|
||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6 h-full">
|
||||
<h3 className="text-lg font-semibold mb-4 text-neutral-900 dark:text-white">
|
||||
Top Referrers
|
||||
</h3>
|
||||
@@ -19,15 +20,16 @@ export default function TopReferrers({ referrers }: TopReferrersProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6">
|
||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6 h-full">
|
||||
<h3 className="text-lg font-semibold mb-4 text-neutral-900 dark:text-white">
|
||||
Top Referrers
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{referrers.map((ref, index) => (
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<div className="flex-1 truncate text-neutral-900 dark:text-white">
|
||||
{ref.referrer}
|
||||
<div className="flex-1 truncate text-neutral-900 dark:text-white flex items-center gap-3">
|
||||
<span className="text-lg flex-shrink-0">{getReferrerIcon(ref.referrer)}</span>
|
||||
<span className="truncate" title={ref.referrer}>{ref.referrer}</span>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
{formatNumber(ref.pageviews)}
|
||||
|
||||
88
lib/utils/icons.tsx
Normal file
88
lib/utils/icons.tsx
Normal file
@@ -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 <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" />
|
||||
}
|
||||
|
||||
export function getOSIcon(osName: string) {
|
||||
if (!osName) return <MdDeviceUnknown 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" />
|
||||
|
||||
return <MdDeviceUnknown className="text-neutral-400" />
|
||||
}
|
||||
|
||||
export function getDeviceIcon(deviceName: string) {
|
||||
if (!deviceName) return <MdDeviceUnknown 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" />
|
||||
|
||||
return <MdDeviceUnknown className="text-neutral-400" />
|
||||
}
|
||||
|
||||
export function getReferrerIcon(referrerName: string) {
|
||||
if (!referrerName) return <FaGlobe 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 <FaTwitter className="text-blue-400" />
|
||||
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" />
|
||||
|
||||
// Try to use a generic globe or maybe check if it is a URL
|
||||
return <FaGlobe className="text-neutral-400" />
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user