Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
180 changes: 96 additions & 84 deletions client/src/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
//src/app/admin/page.tsx
// Admin dashboard page
"use client";

import React, { useEffect, useState } from 'react';
import React, { useEffect, useState } from "react";
import messages from "@/constants/messages";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Table, TableHead, TableRow, TableHeaderCell, TableBody, TableCell } from "@/components/ui/table";
import {
Table,
TableHead,
TableRow,
TableHeaderCell,
TableBody,
TableCell,
} from "@/components/ui/table";
import { useRouter } from "next/navigation";

interface ApiStat {
Expand All @@ -15,119 +20,120 @@ interface ApiStat {
}

interface UserStat {
username: string;
email: string;
token: string;
totalRequests: number;
}

const AdminDashboard: React.FC = () => {
const [apiStats, setApiStats] = useState<ApiStat[]>([]);
//will be implemented later using new api endpoint eg. ${process.env.NEXT_PUBLIC_API_URL}/api_stats
const [userStats, setUserStats] = useState<UserStat[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const router = useRouter();

useEffect(() => {
const checkAuth = async () => {
const fetchData = async () => {
try {
const response = await fetch(
// Authenticate admin user
const authResponse = await fetch(
`${process.env.NEXT_PUBLIC_USER_DATABASE}/verify-token`,
{
method: "GET",
credentials: "include", // Include cookies in the request
credentials: "include",
}
);
if (response.status !== 200) {
router.push("/login");
return;
}

const data = await response.json();
console.log("Verification response:", data.info.role);
if (!authResponse.ok) {
throw new Error("Authentication failed. Please log in.");
}

if (data.info.role !== "admin") {
router.push("/login");
const authData = await authResponse.json();
if (authData.info.role !== "admin") {
router.replace("/login");
return;
}

// Handle verification response here
} catch (error) {
console.error("Error:", error);
}
};

const fetchApiData = async () => {
try {
// Retrieve the JWT token from cookies
// const token = document.cookie
// .split('; ')
// .find(row => row.startsWith('authToken='))
// ?.split('=')[1];

// if (!token) {
// setError(messages.auth.notAuthenticated);
// return;
// }

// Fetch all user information
const userResponse = await fetch(`${process.env.NEXT_PUBLIC_USER_DATABASE}/users`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
// Fetch API call and user data
const [apiCallsResponse, usersResponse] = await Promise.all([
fetch(`${process.env.NEXT_PUBLIC_USER_DATABASE}/api-calls`, {
method: "GET",
headers: { "Content-Type": "application/json" },
}),
fetch(`${process.env.NEXT_PUBLIC_USER_DATABASE}/users`, {
method: "GET",
headers: { "Content-Type": "application/json" },
}),
]);

if (!userResponse.ok) {
throw new Error(messages.fetch.userStatsError);
if (!apiCallsResponse.ok || !usersResponse.ok) {
throw new Error("Failed to fetch data.");
}

const users = await userResponse.json();
const apiCalls = await apiCallsResponse.json();
const users = await usersResponse.json();

// Fetch API stats for each user
const userStatsPromises = users.map(async (user: { id: string; email: string}) => {
const userStatsResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api_count?user_id=${user.id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});

if (userStatsResponse.ok) {
const { api_count: totalRequests } = await userStatsResponse.json();
return {
email: user.email,
totalRequests,
};
// Aggregate API stats by method and endpoint
const apiStatsMap = new Map<string, ApiStat>();
apiCalls.forEach((call: { http_method: string; endpoint: string }) => {
const key = `${call.http_method} ${call.endpoint}`;
if (apiStatsMap.has(key)) {
const stat = apiStatsMap.get(key)!;
stat.requests += 1;
} else {
console.error(`${messages.fetch.userStatsError} for user ID: ${user.id}`);
return null;
apiStatsMap.set(key, {
method: call.http_method,
endpoint: call.endpoint,
requests: 1,
});
}
});

const userStatsResults = await Promise.all(userStatsPromises);
setUserStats(userStatsResults.filter(Boolean) as UserStat[]); // Remove any null entries
} catch (error) {
if (error instanceof Error) {
setError(error.message);
} else {
setError(messages.fetch.unknownError);
}
console.error(messages.fetch.unknownError, error);
const sortedApiStats = Array.from(apiStatsMap.values()).sort((a, b) => {
if (a.endpoint === b.endpoint) {
return a.method.localeCompare(b.method);
}
return a.endpoint.localeCompare(b.endpoint);
});
setApiStats(sortedApiStats);

// Aggregate user stats
const userStatsMap = new Map<string, number>();
apiCalls.forEach((call: { user_id: string }) => {
if (userStatsMap.has(call.user_id)) {
userStatsMap.set(call.user_id, userStatsMap.get(call.user_id)! + 1);
} else {
userStatsMap.set(call.user_id, 1);
}
});

const userStatsArray: UserStat[] = users.map(
(user: { id: string; email: string; username?: string }) => ({
username: user.username || `User ID: ${user.id}`,
email: user.email,
totalRequests: userStatsMap.get(user.id) || 0,
})
);

setUserStats(userStatsArray);
} catch (error: any) {
setError(error.message || "An error occurred.");
console.error(error);
} finally {
setLoading(false);
}
};

checkAuth();
fetchApiData();
fetchData();
}, []);

return (
<div className="flex justify-center items-center min-h-screen bg-gray-100 p-4">
<Card className="w-full max-w-2xl bg-white shadow-md rounded-lg">
<Card className="w-full max-w-4xl bg-white shadow-md rounded-lg">
<CardHeader>
<CardTitle className="text-center text-xl font-bold">{messages.dashboard.title}</CardTitle>
<CardTitle className="text-center text-xl font-bold">
{messages.dashboard.title}
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{loading ? (
Expand All @@ -136,7 +142,10 @@ const AdminDashboard: React.FC = () => {
<p className="text-center text-red-500">{error}</p>
) : (
<>
<h2 className="text-lg font-semibold mt-4">{messages.dashboard.apiStatsTitle}</h2>
{/* API Stats Table */}
<h2 className="text-lg font-semibold mt-4">
{messages.dashboard.apiStatsTitle}
</h2>
<Table>
<TableHead>
<TableRow>
Expand All @@ -156,37 +165,40 @@ const AdminDashboard: React.FC = () => {
))
) : (
<TableRow>
<td colSpan={3} className="text-center">
<TableCell className="text-center">
{messages.table.noApiStats}
</td>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>

<h2 className="text-lg font-semibold mt-4">{messages.dashboard.userStatsTitle}</h2>
{/* User Stats Table */}
<h2 className="text-lg font-semibold mt-4">
{messages.dashboard.userStatsTitle}
</h2>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Username</TableHeaderCell>
<TableHeaderCell>Email</TableHeaderCell>
<TableHeaderCell>Token</TableHeaderCell>
<TableHeaderCell>Total Requests</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{userStats.length ? (
userStats.map((user, index) => (
<TableRow key={index}>
<TableCell>{user.username}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.token}</TableCell>
<TableCell>{user.totalRequests}</TableCell>
</TableRow>
))
) : (
<TableRow>
<td colSpan={4} className="text-center">
<TableCell className="text-center">
{messages.table.noUserStats}
</td>
</TableCell>
</TableRow>
)}
</TableBody>
Expand Down
78 changes: 78 additions & 0 deletions client/src/app/dashboard/forgotPassword/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use client";

import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import Layout from '@/components/Layout';
import Link from "next/link";
import { FormEvent, useState } from "react";

export default function ForgotPassword() {
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [messageType, setMessageType] = useState<"success" | "error" | "">("");
const [loading, setLoading] = useState(false);

const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
setLoading(true);

// Call API to initiate the password reset
const response = await fetch(
`${process.env.NEXT_PUBLIC_USER_DATABASE}/forgotPassword`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
}
);

if (response.ok) {
setMessage("Password reset link sent to your email.");
setMessageType("success");
} else {
const errorData = await response.json();
setMessage(`Error: ${errorData.message}`);
setMessageType("error");
}

setLoading(false);
};

return (

<Layout>
<div className="flex flex-col items-center justify-top min-h-screen">
<h1 className="text-3xl font-bold mb-8 mt-8">Forgot Password</h1>
<div className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-4">
<Input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Sending..." : "Send Reset Link"}
</Button>
</form>
{message && (
<p
className={`mt-4 ${
messageType === "success" ? "text-green-500" : "text-red-500"
}`}
>
{message}
</p>
)}
<Button variant="secondary" className="w-full">
<Link href="/dashboard/login" className="flex items-center">
Back to Login
</Link>
</Button>
</div>
</div>
</Layout>
);
}
Loading