feat(app): add dashboard pages for settings, admin, and public user profiles

Introduce new pages for profile settings, admin dashboard (users, contents, categories), and public user profiles. Enhance profile functionality with avatar uploads and bio updates. Include help and improved content trends/recent pages. Streamline content display using `HomeContent`.
This commit is contained in:
Mathis HERRIOT
2026-01-14 21:43:27 +01:00
parent 026aebaee3
commit fb7ddde42e
10 changed files with 842 additions and 28 deletions

View File

@@ -0,0 +1,123 @@
"use client";
import { useEffect, useState } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { UserService } from "@/services/user.service";
import type { User } from "@/types/user";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { Trash2 } from "lucide-react";
export default function AdminUsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [totalCount, setTotalCount] = useState(0);
useEffect(() => {
UserService.getUsersAdmin()
.then((res) => {
setUsers(res.data);
setTotalCount(res.totalCount);
})
.catch(err => {
console.error(err);
})
.finally(() => setLoading(false));
}, []);
const handleDelete = async (uuid: string) => {
if (!confirm("Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.")) return;
try {
await UserService.removeUserAdmin(uuid);
setUsers(users.filter(u => u.uuid !== uuid));
setTotalCount(prev => prev - 1);
} catch (error) {
console.error(error);
}
};
return (
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">Utilisateurs ({totalCount})</h2>
</div>
<div className="rounded-md border bg-card">
<Table>
<TableHeader>
<TableRow>
<TableHead>Utilisateur</TableHead>
<TableHead>Email</TableHead>
<TableHead>Rôle</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date d'inscription</TableHead>
<TableHead className="w-[50px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
Array.from({ length: 5 }).map((_, i) => (
<TableRow key={i}>
<TableCell><Skeleton className="h-4 w-[150px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[200px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[50px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[80px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[100px]" /></TableCell>
</TableRow>
))
) : users.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className="text-center h-24">
Aucun utilisateur trouvé.
</TableCell>
</TableRow>
) : (
users.map((user) => (
<TableRow key={user.uuid}>
<TableCell className="font-medium whitespace-nowrap">
{user.displayName || user.username}
<div className="text-xs text-muted-foreground">@{user.username}</div>
</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Badge variant={user.role === "admin" ? "default" : "secondary"}>
{user.role}
</Badge>
</TableCell>
<TableCell>
<Badge variant={user.status === "active" ? "success" : "destructive"}>
{user.status}
</Badge>
</TableCell>
<TableCell className="whitespace-nowrap">
{format(new Date(user.createdAt), "PPP", { locale: fr })}
</TableCell>
<TableCell>
<Button
variant="ghost"
size="icon"
onClick={() => handleDelete(user.uuid)}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
);
}