Skip to content

Commit 7a08913

Browse files
committed
feat: added dashboard for active sessions
1 parent 2254e92 commit 7a08913

File tree

4 files changed

+183
-5
lines changed

4 files changed

+183
-5
lines changed

frontend/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@radix-ui/react-tooltip": "^1.2.8",
2020
"@tailwindcss/vite": "^4.1.14",
2121
"@tanstack/react-query": "^5.90.3",
22-
"axios": "^1.12.2",
22+
"axios": "^1.13.2",
2323
"class-variance-authority": "^0.7.1",
2424
"clsx": "^2.1.1",
2525
"framer-motion": "^12.23.24",

frontend/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import JoinRoom from "./pages/JoinRoom.js";
1414
import InRoom from "./pages/InRoom.js";
1515
import CreateRoomLobby from "./pages/CreateRoomLobby.js";
1616
import CreateRoom from "./pages/CreateRoom.js";
17+
import ActiveSessions from "./pages/ActiveSessions.js";
1718
const queryClient = new QueryClient();
1819

1920
const App = () => (
@@ -33,6 +34,7 @@ const App = () => (
3334
<Route path="/join-room" element={<JoinRoom />} /> {/* ✅ */}
3435
<Route path="/room/:roomName" element={<InRoom />} />
3536
<Route path="/lobby/:roomId" element={<CreateRoomLobby />} />
37+
<Route path="/sessions" element={<ActiveSessions />} />
3638
</Routes>
3739
</BrowserRouter>
3840
</TooltipProvider>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import React, { useEffect, useState } from "react";
2+
import { motion } from "framer-motion";
3+
import { Button } from "../components/ui/button.js";
4+
import { Card, CardContent } from "../components/ui/card.js";
5+
import {
6+
Loader2,
7+
Laptop,
8+
Smartphone,
9+
MonitorSmartphone,
10+
XCircle,
11+
CheckCircle2,
12+
} from "lucide-react";
13+
14+
interface Session {
15+
_id: string;
16+
userAgent: string;
17+
ipAddress: string;
18+
createdAt: string;
19+
}
20+
21+
const dummySessions: Session[] = [
22+
{
23+
_id: "1",
24+
userAgent: "Chrome on Windows 11",
25+
ipAddress: "192.168.1.24",
26+
createdAt: "2025-11-08T12:15:00Z",
27+
},
28+
{
29+
_id: "2",
30+
userAgent: "Safari on iPhone 15",
31+
ipAddress: "10.0.0.45",
32+
createdAt: "2025-11-07T20:30:00Z",
33+
},
34+
{
35+
_id: "3",
36+
userAgent: "Edge on MacBook Air",
37+
ipAddress: "172.16.0.88",
38+
createdAt: "2025-11-06T09:00:00Z",
39+
},
40+
];
41+
42+
const ActiveSessions: React.FC = () => {
43+
const [sessions, setSessions] = useState<Session[]>([]);
44+
const [loading, setLoading] = useState(true);
45+
const [revokingId, setRevokingId] = useState<string | null>(null);
46+
const currentDevice = "Chrome on Windows 11"; // simulate current device
47+
48+
useEffect(() => {
49+
const timer = setTimeout(() => {
50+
setSessions(dummySessions);
51+
setLoading(false);
52+
}, 1000);
53+
return () => clearTimeout(timer);
54+
}, []);
55+
56+
const revokeSession = (id: string) => {
57+
setRevokingId(id);
58+
setTimeout(() => {
59+
setSessions((prev) => prev.filter((s) => s._id !== id));
60+
setRevokingId(null);
61+
}, 800);
62+
};
63+
64+
const getIcon = (ua: string) => {
65+
if (/iPhone|Android/i.test(ua)) return <Smartphone className="w-6 h-6" />;
66+
if (/Mac|Windows/i.test(ua))
67+
return <Laptop className="w-6 h-6" />;
68+
return <MonitorSmartphone className="w-6 h-6" />;
69+
};
70+
71+
if (loading)
72+
return (
73+
<div className="flex justify-center items-center h-[70vh] text-gray-500">
74+
<Loader2 className="animate-spin w-6 h-6 mr-2" />
75+
Loading sessions...
76+
</div>
77+
);
78+
79+
return (
80+
<motion.div
81+
className="max-w-3xl mx-auto p-6 space-y-6"
82+
initial={{ opacity: 0, y: 15 }}
83+
animate={{ opacity: 1, y: 0 }}
84+
>
85+
<div className="text-center">
86+
<h1 className="text-3xl font-semibold bg-gradient-to-r from-blue-500 to-indigo-500 bg-clip-text text-transparent">
87+
Active Sessions
88+
</h1>
89+
<p className="text-gray-500 mt-2">
90+
Manage your logged-in devices securely.
91+
</p>
92+
</div>
93+
94+
{sessions.length === 0 ? (
95+
<motion.p
96+
className="text-gray-500 text-center mt-20"
97+
initial={{ opacity: 0 }}
98+
animate={{ opacity: 1 }}
99+
>
100+
No active sessions found.
101+
</motion.p>
102+
) : (
103+
<div className="grid gap-4">
104+
{sessions.map((session, i) => {
105+
const isCurrent = session.userAgent === currentDevice;
106+
return (
107+
<motion.div
108+
key={session._id}
109+
initial={{ opacity: 0, y: 10 }}
110+
animate={{ opacity: 1, y: 0 }}
111+
transition={{ delay: i * 0.05 }}
112+
>
113+
<Card
114+
className={`rounded-2xl backdrop-blur-md border ${isCurrent
115+
? "border-blue-400 bg-blue-50/40"
116+
: "border-gray-200 bg-white/60"
117+
} shadow-sm hover:shadow-lg transition-all duration-200`}
118+
>
119+
<CardContent className="flex justify-between items-center p-5">
120+
<div className="flex items-center gap-4">
121+
<div
122+
className={`p-3 rounded-full ${isCurrent
123+
? "bg-blue-500/20 text-blue-600"
124+
: "bg-gray-200/60 text-gray-700"
125+
}`}
126+
>
127+
{getIcon(session.userAgent)}
128+
</div>
129+
130+
<div>
131+
<p className="font-medium text-gray-800 flex items-center gap-2">
132+
{session.userAgent}
133+
{isCurrent && (
134+
<span className="flex items-center gap-1 text-blue-600 text-xs font-semibold">
135+
<CheckCircle2 className="w-3 h-3" /> This Device
136+
</span>
137+
)}
138+
</p>
139+
<p className="text-sm text-gray-500">
140+
IP: {session.ipAddress}
141+
</p>
142+
<p className="text-xs text-gray-400">
143+
Logged in:{" "}
144+
{new Date(session.createdAt).toLocaleString()}
145+
</p>
146+
</div>
147+
</div>
148+
149+
<Button
150+
variant="destructive"
151+
disabled={revokingId === session._id}
152+
onClick={() => revokeSession(session._id)}
153+
className="flex items-center gap-1"
154+
>
155+
{revokingId === session._id ? (
156+
<>
157+
<Loader2 className="animate-spin w-4 h-4" /> Revoking...
158+
</>
159+
) : (
160+
<>
161+
<XCircle className="w-4 h-4" /> Revoke
162+
</>
163+
)}
164+
</Button>
165+
</CardContent>
166+
</Card>
167+
</motion.div>
168+
);
169+
})}
170+
</div>
171+
)}
172+
</motion.div>
173+
);
174+
};
175+
176+
export default ActiveSessions;

0 commit comments

Comments
 (0)