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,98 @@
"use client";
import { Calendar, User as UserIcon } from "lucide-react";
import * as React from "react";
import { ContentList } from "@/components/content-list";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Spinner } from "@/components/ui/spinner";
import { ContentService } from "@/services/content.service";
import { UserService } from "@/services/user.service";
import type { User } from "@/types/user";
export default function PublicProfilePage({
params,
}: {
params: Promise<{ username: string }>;
}) {
const { username } = React.use(params);
const [user, setUser] = React.useState<User | null>(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
UserService.getProfile(username)
.then(setUser)
.catch(console.error)
.finally(() => setLoading(false));
}, [username]);
const fetchUserMemes = React.useCallback(
(params: { limit: number; offset: number }) =>
ContentService.getExplore({ ...params, author: username }),
[username],
);
if (loading) {
return (
<div className="flex h-[400px] items-center justify-center">
<Spinner className="h-8 w-8 text-primary" />
</div>
);
}
if (!user) {
return (
<div className="max-w-2xl mx-auto py-12 px-4 text-center">
<div className="bg-primary/10 p-4 rounded-full w-fit mx-auto mb-4">
<UserIcon className="h-8 w-8 text-primary" />
</div>
<h1 className="text-2xl font-bold">Utilisateur non trouvé</h1>
<p className="text-muted-foreground mt-2">
Le membre @{username} n'existe pas ou a quitté le troupeau.
</p>
</div>
);
}
return (
<div className="max-w-4xl mx-auto py-8 px-4">
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-8 border shadow-sm mb-8">
<div className="flex flex-col md:flex-row items-center gap-8">
<Avatar className="h-32 w-32 border-4 border-primary/10">
<AvatarImage src={user.avatarUrl} alt={user.username} />
<AvatarFallback className="text-4xl">
{user.username.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-1 text-center md:text-left space-y-4">
<div>
<h1 className="text-3xl font-bold">
{user.displayName || user.username}
</h1>
<p className="text-muted-foreground">@{user.username}</p>
</div>
{user.bio && (
<p className="max-w-md text-sm leading-relaxed mx-auto md:mx-0">
{user.bio}
</p>
)}
<div className="flex flex-wrap justify-center md:justify-start gap-4 text-sm text-muted-foreground">
<span className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
Membre depuis{" "}
{new Date(user.createdAt).toLocaleDateString("fr-FR", {
month: "long",
year: "numeric",
})}
</span>
</div>
</div>
</div>
</div>
<div className="space-y-8">
<h2 className="text-xl font-bold border-b pb-4">Ses mèmes</h2>
<ContentList fetchFn={fetchUserMemes} />
</div>
</div>
);
}