Skip to content
Merged
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
38 changes: 27 additions & 11 deletions app/Admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,51 @@ import Header from "../Components/Area_banner"
import Sidebar from "../Components/Area_sidebar"
import UsersPanel from "../Components/User_panel"
import ServicesPanel from "../Components/Service_panel"

import { Menu, X } from "lucide-react"

export default function AdminDashboard() {
const [tab, setTab] = useState<"users" | "services">("users")
const [sidebarOpen, setSidebarOpen] = useState(false)

return (
<div className="min-h-screen bg-[#FFFAFA] flex flex-col">
<Header />
<Sidebar />

<main className="ml-60 p-10 flex-1">
<h1 className="text-4xl font-bold mb-10 text-black">Admin Dashboard</h1>

<div className="flex gap-6 mb-10">
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="fixed top-4 right-4 z-50 p-2 bg-white rounded-lg shadow-lg lg:hidden hover:bg-gray-100 transition-colors"
aria-label="Toggle menu"
>
{sidebarOpen ? <X size={24} className="text-[#1B264F]" /> : <Menu size={24} className="text-[#1B264F]" />}
</button>
{sidebarOpen && (
<div
className="fixed inset-0 bg-opacity-50 z-30 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
<div className={`
fixed top-0 left-0 h-full z-40 transition-transform duration-300 ease-in-out
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'} lg:translate-x-0
`}>
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
</div>
<main className="ml-0 lg:ml-60 p-4 sm:p-6 md:p-8 lg:p-10 flex-1">
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold mb-6 md:mb-10 text-black">Admin Dashboard</h1>
<div className="flex flex-col sm:flex-row gap-3 sm:gap-6 mb-6 md:mb-10">
{["users", "services"].map((t) => (
<button
key={t}
onClick={() => setTab(t as any)}
className={`px-6 py-2 rounded-md text-lg font-medium transition
${tab === t ? "bg-[#1B264F] text-white" : "bg-[#5A80F0] border hover:bg-gray-500"}`}
className={`w-full sm:w-auto px-6 py-2 rounded-md text-base sm:text-lg font-medium transition
${tab === t ? "bg-[#1B264F] text-white" : "bg-[#5A80F0] text-white hover:bg-[#4a6cd1]"}`}
>
{t.toUpperCase()}
</button>
))}
</div>

{tab === "users" && <UsersPanel />}
{tab === "services" && <ServicesPanel />}
</main>
</div>
)
}
}
23 changes: 17 additions & 6 deletions app/Components/Area_sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";

export default function Sidebar() {
interface SidebarProps {
isOpen?: boolean;
onClose?: () => void;
}

export default function Sidebar({ isOpen = true, onClose }: SidebarProps) {
const pathname = usePathname();
const baseStyle = "px-6 py-8 text-xl cursor-pointer block w-full h-full";
const hoverStyle = "hover:text-black hover:bg-[#FFFAFA]";
Expand Down Expand Up @@ -65,22 +70,26 @@ export default function Sidebar() {
init();
}, []);

useEffect(() => {
}, [userRole]);
const handleLinkClick = () => {
if (onClose) {
onClose();
}
};

if (loading) {
return (
<div className="absolute left-0 top-0 h-full w-60 bg-[#1B264F] z-15" />
<div className="h-full w-60 bg-[#1B264F] z-15" />
);
}

return (
<div className="absolute left-0 top-0 h-full w-60 bg-[#1B264F] z-15">
<nav className="mt-15">
<div className="h-full w-60 bg-[#1B264F] z-15">
<nav className="mt-16">
<ul>
<li>
<Link
href="/Dashboard"
onClick={handleLinkClick}
className={`${baseStyle} ${
pathname === "/Dashboard"
? "bg-[#FFFAFA] text-black"
Expand All @@ -93,6 +102,7 @@ export default function Sidebar() {
<li>
<Link
href="/Services"
onClick={handleLinkClick}
className={`${baseStyle} ${
pathname === "/Services"
? "bg-[#FFFAFA] text-black"
Expand All @@ -106,6 +116,7 @@ export default function Sidebar() {
<li>
<Link
href="/Admin"
onClick={handleLinkClick}
className={`${baseStyle} ${
pathname === "/Admin"
? "bg-[#FFFAFA] text-black"
Expand Down
28 changes: 14 additions & 14 deletions app/Components/Service_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ type ServiceStats = {
reactionsCount: number
}

const countServiceStats = (aboutData: any): ServiceStats[] => {
const countServiceStats = (aboutData: any): ServiceStats[] => {
let servicesArray: any[] = []

if (Array.isArray(aboutData)) {
servicesArray = aboutData
} else if (aboutData?.server?.services && Array.isArray(aboutData.server.services)) {
Expand All @@ -20,7 +20,7 @@ const countServiceStats = (aboutData: any): ServiceStats[] => {
return servicesArray.map((service: any) => {
const actionsCount = (service.actions && Array.isArray(service.actions)) ? service.actions.length : 0
const reactionsCount = (service.reactions && Array.isArray(service.reactions)) ? service.reactions.length : 0

return {
name: service.name,
actionsCount,
Expand All @@ -38,14 +38,14 @@ export default function ServicesPanel() {
const fetchServices = async () => {
try {
const response = await fetch("http://localhost:8080/about.json")

if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}

const data = await response.json()
const data = await response.json()
const formattedServices = countServiceStats(data)

setServices(formattedServices)
} catch (err) {
console.error("Error fetching services:", err)
Expand All @@ -61,40 +61,40 @@ export default function ServicesPanel() {
if (loading) {
return (
<div className="flex items-center justify-center min-h-96">
<p className="text-white text-lg">Chargement des services...</p>
<p className="text-black text-base sm:text-lg">Chargement des services...</p>
</div>
)
}

if (error) {
return (
<div className="flex items-center justify-center min-h-96">
<p className="text-red-400 text-lg">{error}</p>
<p className="text-red-500 text-base sm:text-lg">{error}</p>
</div>
)
}

if (services.length === 0) {
return (
<div className="flex items-center justify-center min-h-96">
<p className="text-white/60 text-lg">Aucun service trouvé</p>
<p className="text-gray-600 text-base sm:text-lg">Aucun service trouvé</p>
</div>
)
}

return (
<div className="grid grid-cols-3 gap-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{services.map((s) => (
<div
key={s.name}
className="bg-[#1B264F] rounded-xl shadow p-6 hover:shadow-lg transition"
className="bg-[#1B264F] rounded-xl shadow p-4 sm:p-6 hover:shadow-lg transition"
>
<h3 className="text-xl font-bold mb-4 text-white">{s.name}</h3>
<h3 className="text-lg sm:text-xl font-bold mb-3 sm:mb-4 text-white">{s.name}</h3>
<div className="space-y-2">
<p className="text-white/80">
<p className="text-sm sm:text-base text-white/80">
<span className="font-semibold">Actions:</span> {s.actionsCount}
</p>
<p className="text-white/80">
<p className="text-sm sm:text-base text-white/80">
<span className="font-semibold">Reactions:</span> {s.reactionsCount}
</p>
</div>
Expand Down
138 changes: 89 additions & 49 deletions app/Components/User_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,77 +77,117 @@ export default function UsersPanel() {

if (loading) {
return (
<div className="bg-[#1B264F] rounded-xl shadow p-6">
<h2 className="text-2xl font-bold mb-6 text-white">Users</h2>
<div className="bg-[#1B264F] rounded-xl shadow p-4 sm:p-6">
<h2 className="text-xl sm:text-2xl font-bold mb-4 sm:mb-6 text-white">Users</h2>
<p className="text-white/60">Chargement...</p>
</div>
)
}

if (!isAdmin || error) {
return (
<div className="bg-[#1B264F] rounded-xl shadow p-6 flex items-center justify-center min-h-96">
<div className="bg-[#1B264F] rounded-xl shadow p-4 sm:p-6 flex items-center justify-center min-h-96">
<div className="text-center">
<h2 className="text-2xl font-bold mb-4 text-red-400">Access denied</h2>
<p className="text-white/60">You do not have the necessary permissions to access this page.</p>
<h2 className="text-xl sm:text-2xl font-bold mb-4 text-red-400">Access denied</h2>
<p className="text-sm sm:text-base text-white/60">You do not have the necessary permissions to access this page.</p>
</div>
</div>
)
}

if (users.length === 0) {
return (
<div className="bg-[#1B264F] rounded-xl shadow p-6">
<h2 className="text-2xl font-bold mb-6 text-white">Users</h2>
<div className="bg-[#1B264F] rounded-xl shadow p-4 sm:p-6">
<h2 className="text-xl sm:text-2xl font-bold mb-4 sm:mb-6 text-white">Users</h2>
<p className="text-white/60">Aucun utilisateur trouvé</p>
</div>
)
}

return (
<div className="bg-[#1B264F] rounded-xl shadow p-6">
<h2 className="text-2xl font-bold mb-6 text-white">Users</h2>
<table className="w-full text-left text-white">
<thead>
<tr className="border-b border-white/20">
<th className="pb-3">Email</th>
<th>Status</th>
<th>Role</th>
</tr>
</thead>
<tbody>
{users.map((u) => (
<tr key={u.id} className="border-b border-white/10 last:border-0">
<td className="py-4">{u.email}</td>
<td>
<span
onClick={() => changeAuthorizationStatus(u.id, !u.authorized)}
className={`px-3 py-1 rounded-full text-sm font-medium cursor-pointer
${
u.authorized
? "bg-green-500/20 text-green-300"
: "bg-red-500/20 text-red-300"
}`}
>
{u.authorized ? "AUTHORIZED" : "UNAUTHORIZED"}
</span>
</td>
<td>
<span
className={`px-3 py-1 rounded-full text-sm font-medium
${
u.admin === true
? "bg-purple-500/20 text-purple-300"
: "bg-blue-500/20 text-blue-300"
}`}
>
{u.admin ? "ADMIN" : "USER"}
</span>
</td>
<div className="bg-[#1B264F] rounded-xl shadow p-4 sm:p-6">
<h2 className="text-xl sm:text-2xl font-bold mb-4 sm:mb-6 text-white">Users</h2>
<div className="hidden md:block overflow-x-auto">
<table className="w-full text-left text-white">
<thead>
<tr className="border-b border-white/20">
<th className="pb-3">Email</th>
<th>Status</th>
<th>Role</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{users.map((u) => (
<tr key={u.id} className="border-b border-white/10 last:border-0">
<td className="py-4">{u.email}</td>
<td>
<span
onClick={() => changeAuthorizationStatus(u.id, !u.authorized)}
className={`px-3 py-1 rounded-full text-sm font-medium cursor-pointer
${
u.authorized
? "bg-green-500/20 text-green-300"
: "bg-red-500/20 text-red-300"
}`}
>
{u.authorized ? "AUTHORIZED" : "UNAUTHORIZED"}
</span>
</td>
<td>
<span
className={`px-3 py-1 rounded-full text-sm font-medium
${
u.admin === true
? "bg-purple-500/20 text-purple-300"
: "bg-blue-500/20 text-blue-300"
}`}
>
{u.admin ? "ADMIN" : "USER"}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>

<div className="md:hidden space-y-4">
{users.map((u) => (
<div key={u.id} className="bg-white/5 rounded-lg p-4 space-y-3">
<div className="break-all">
<p className="text-white/60 text-sm mb-1">Email</p>
<p className="text-white font-medium">{u.email}</p>
</div>
<div>
<p className="text-white/60 text-sm mb-2">Status</p>
<span
onClick={() => changeAuthorizationStatus(u.id, !u.authorized)}
className={`inline-block px-3 py-1 rounded-full text-sm font-medium cursor-pointer
${
u.authorized
? "bg-green-500/20 text-green-300"
: "bg-red-500/20 text-red-300"
}`}
>
{u.authorized ? "AUTHORIZED" : "UNAUTHORIZED"}
</span>
</div>
<div>
<p className="text-white/60 text-sm mb-2">Role</p>
<span
className={`inline-block px-3 py-1 rounded-full text-sm font-medium
${
u.admin === true
? "bg-purple-500/20 text-purple-300"
: "bg-blue-500/20 text-blue-300"
}`}
>
{u.admin ? "ADMIN" : "USER"}
</span>
</div>
</div>
))}
</div>
</div>
)
}
Loading