Skip to content

Commit 5c0ff6b

Browse files
authored
Merge pull request #418 from Peppermint-Lab/main
next
2 parents 8a50dfb + eec566e commit 5c0ff6b

File tree

2 files changed

+138
-1
lines changed

2 files changed

+138
-1
lines changed

apps/client/layouts/settings.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { classNames } from "@/shadcn/lib/utils";
22
import { SidebarProvider } from "@/shadcn/ui/sidebar";
3-
import { Bell, Flag, KeyRound } from "lucide-react";
3+
import { Bell, Flag, KeyRound, SearchSlashIcon } from "lucide-react";
44
import useTranslation from "next-translate/useTranslation";
55
import Link from "next/link";
66
import { useRouter } from "next/router";
@@ -57,6 +57,19 @@ export default function Settings({ children }) {
5757
<Flag className="flex-shrink-0 h-5 w-5 text-foreground" />
5858
<span>Feature Flags</span>
5959
</Link>
60+
61+
<Link
62+
href="/settings/sessions"
63+
className={classNames(
64+
router.pathname === "/settings/sessions"
65+
? "bg-secondary dark:bg-primary"
66+
: "hover:bg-[#F0F3F9] dark:hover:bg-white dark:hover:text-gray-900 ",
67+
"group flex items-center gap-x-3 py-2 px-3 rounded-md text-sm font-semibold leading-6"
68+
)}
69+
>
70+
<SearchSlashIcon className="flex-shrink-0 h-5 w-5 text-foreground" />
71+
<span>Sessions</span>
72+
</Link>
6073
</nav>
6174
</aside>
6275

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { toast } from "@/shadcn/hooks/use-toast";
2+
import { Button } from "@/shadcn/ui/button";
3+
import { getCookie } from "cookies-next";
4+
import { useEffect, useState } from "react";
5+
6+
interface Session {
7+
id: string;
8+
userAgent: string;
9+
ipAddress: string;
10+
createdAt: string;
11+
expires: string;
12+
}
13+
14+
function getPrettyUserAgent(userAgent: string) {
15+
// Extract browser and OS
16+
const browser =
17+
userAgent
18+
.match(/(Chrome|Safari|Firefox|Edge)\/[\d.]+/)?.[0]
19+
.split("/")[0] ?? "Unknown Browser";
20+
const os = userAgent.match(/\((.*?)\)/)?.[1].split(";")[0] ?? "Unknown OS";
21+
22+
return `${browser} on ${os}`;
23+
}
24+
25+
export default function Sessions() {
26+
const [sessions, setSessions] = useState<Session[]>([]);
27+
28+
const fetchSessions = async () => {
29+
try {
30+
const response = await fetch("/api/v1/auth/sessions", {
31+
headers: {
32+
Authorization: `Bearer ${getCookie("session")}`,
33+
},
34+
});
35+
if (!response.ok) {
36+
throw new Error("Failed to fetch sessions");
37+
}
38+
const data = await response.json();
39+
setSessions(data.sessions);
40+
} catch (error) {
41+
console.error("Error fetching sessions:", error);
42+
43+
toast({
44+
variant: "destructive",
45+
title: "Error fetching sessions",
46+
description: "Please try again later",
47+
});
48+
}
49+
};
50+
51+
useEffect(() => {
52+
fetchSessions();
53+
}, []);
54+
55+
const revokeSession = async (sessionId: string) => {
56+
try {
57+
const response = await fetch(`/api/v1/auth/sessions/${sessionId}`, {
58+
headers: {
59+
Authorization: `Bearer ${getCookie("session")}`,
60+
},
61+
method: "DELETE",
62+
});
63+
64+
if (!response.ok) {
65+
throw new Error("Failed to revoke session");
66+
}
67+
68+
toast({
69+
title: "Session revoked",
70+
description: "The session has been revoked",
71+
});
72+
73+
fetchSessions();
74+
} catch (error) {
75+
console.error("Error revoking session:", error);
76+
}
77+
};
78+
79+
return (
80+
<div className="p-6">
81+
<div className="flex flex-col space-y-1 mb-4">
82+
<h1 className="text-2xl font-bold">Active Sessions</h1>
83+
<span className="text-sm text-foreground">
84+
Devices you are logged in to
85+
</span>
86+
</div>
87+
<div className="space-y-4">
88+
{sessions &&
89+
sessions.map((session) => (
90+
<div
91+
key={session.id}
92+
className="flex flex-row items-center justify-between p-4 border rounded-lg group"
93+
>
94+
<div>
95+
<div className="text-base font-bold">
96+
{session.ipAddress === "::1"
97+
? "Localhost"
98+
: session.ipAddress}
99+
</div>
100+
<div className="font-bold text-xs">
101+
{getPrettyUserAgent(session.userAgent)}
102+
</div>
103+
<div className="text-xs text-foreground">
104+
Created: {new Date(session.createdAt).toLocaleString("en-GB")}
105+
</div>
106+
<div className="text-xs text-foreground">
107+
Expires: {new Date(session.expires).toLocaleString("en-GB")}
108+
</div>
109+
</div>
110+
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
111+
<Button
112+
size="sm"
113+
onClick={() => revokeSession(session.id)}
114+
variant="destructive"
115+
>
116+
Revoke
117+
</Button>
118+
</div>
119+
</div>
120+
))}
121+
</div>
122+
</div>
123+
);
124+
}

0 commit comments

Comments
 (0)