A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.
1import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; 2import { 3 LineChart, 4 Line, 5 XAxis, 6 YAxis, 7 CartesianGrid, 8 Tooltip, 9 ResponsiveContainer, 10} from "recharts"; 11import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 12import { useState, useEffect } from "react"; 13 14import { getLinkClickStats, getLinkSourceStats } from '../api/client'; 15import { ClickStats, SourceStats } from '../types/api'; 16 17interface StatisticsModalProps { 18 isOpen: boolean; 19 onClose: () => void; 20 linkId: number; 21} 22 23export function StatisticsModal({ isOpen, onClose, linkId }: StatisticsModalProps) { 24 const [clicksOverTime, setClicksOverTime] = useState<ClickStats[]>([]); 25 const [sourcesData, setSourcesData] = useState<SourceStats[]>([]); 26 const [loading, setLoading] = useState(true); 27 28 useEffect(() => { 29 if (isOpen && linkId) { 30 const fetchData = async () => { 31 try { 32 setLoading(true); 33 const [clicksData, sourcesData] = await Promise.all([ 34 getLinkClickStats(linkId), 35 getLinkSourceStats(linkId), 36 ]); 37 setClicksOverTime(clicksData); 38 setSourcesData(sourcesData); 39 } catch (error) { 40 console.error("Failed to fetch statistics:", error); 41 } finally { 42 setLoading(false); 43 } 44 }; 45 46 fetchData(); 47 } 48 }, [isOpen, linkId]); 49 50 return ( 51 <Dialog open={isOpen} onOpenChange={onClose}> 52 <DialogContent className="max-w-3xl"> 53 <DialogHeader> 54 <DialogTitle>Link Statistics</DialogTitle> 55 </DialogHeader> 56 57 {loading ? ( 58 <div className="flex items-center justify-center h-64">Loading...</div> 59 ) : ( 60 <div className="grid gap-4"> 61 <Card> 62 <CardHeader> 63 <CardTitle>Clicks Over Time</CardTitle> 64 </CardHeader> 65 <CardContent> 66 <div className="h-[300px]"> 67 <ResponsiveContainer width="100%" height="100%"> 68 <LineChart data={clicksOverTime}> 69 <CartesianGrid strokeDasharray="3 3" /> 70 <XAxis dataKey="date" /> 71 <YAxis /> 72 <Tooltip /> 73 <Line 74 type="monotone" 75 dataKey="clicks" 76 stroke="#8884d8" 77 strokeWidth={2} 78 /> 79 </LineChart> 80 </ResponsiveContainer> 81 </div> 82 </CardContent> 83 </Card> 84 85 <Card> 86 <CardHeader> 87 <CardTitle>Top Sources</CardTitle> 88 </CardHeader> 89 <CardContent> 90 <ul className="space-y-2"> 91 {sourcesData.map((source, index) => ( 92 <li 93 key={source.source} 94 className="flex items-center justify-between py-2 border-b last:border-0" 95 > 96 <span className="text-sm"> 97 <span className="font-medium text-muted-foreground mr-2"> 98 {index + 1}. 99 </span> 100 {source.source} 101 </span> 102 <span className="text-sm font-medium"> 103 {source.count} clicks 104 </span> 105 </li> 106 ))} 107 </ul> 108 </CardContent> 109 </Card> 110 </div> 111 )} 112 </DialogContent> 113 </Dialog> 114 ); 115}