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}