Standardize import declarations, resolve misplaced imports, and enhance consistency across components. Update indentation, split multiline JSX props, and enforce consistent function formatting for better maintainability.
180 lines
5.5 KiB
TypeScript
180 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import { Calendar, Camera, LogIn, LogOut, Settings } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { useSearchParams } from "next/navigation";
|
|
import * as React from "react";
|
|
import { toast } from "sonner";
|
|
import { ContentList } from "@/components/content-list";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { Spinner } from "@/components/ui/spinner";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { useAuth } from "@/providers/auth-provider";
|
|
import { ContentService } from "@/services/content.service";
|
|
import { FavoriteService } from "@/services/favorite.service";
|
|
import { UserService } from "@/services/user.service";
|
|
|
|
export default function ProfilePage() {
|
|
const { user, isAuthenticated, isLoading, logout, refreshUser } = useAuth();
|
|
const searchParams = useSearchParams();
|
|
const tab = searchParams.get("tab") || "memes";
|
|
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
|
|
|
const handleAvatarClick = () => {
|
|
fileInputRef.current?.click();
|
|
};
|
|
|
|
const handleFileChange = async (
|
|
event: React.ChangeEvent<HTMLInputElement>,
|
|
) => {
|
|
const file = event.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
try {
|
|
await UserService.updateAvatar(file);
|
|
toast.success("Avatar mis à jour avec succès !");
|
|
await refreshUser?.();
|
|
} catch (error) {
|
|
console.error(error);
|
|
toast.error("Erreur lors de la mise à jour de l'avatar.");
|
|
}
|
|
};
|
|
|
|
const fetchMyMemes = React.useCallback(
|
|
(params: { limit: number; offset: number }) =>
|
|
ContentService.getExplore({ ...params, author: user?.username }),
|
|
[user?.username],
|
|
);
|
|
|
|
const fetchMyFavorites = React.useCallback(
|
|
(params: { limit: number; offset: number }) => FavoriteService.list(params),
|
|
[],
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-[400px] items-center justify-center">
|
|
<Spinner className="h-8 w-8 text-primary" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!isAuthenticated || !user) {
|
|
return (
|
|
<div className="max-w-2xl mx-auto py-8 px-4 text-center">
|
|
<Card className="p-12">
|
|
<CardHeader>
|
|
<div className="mx-auto bg-primary/10 p-4 rounded-full w-fit mb-4">
|
|
<LogIn className="h-8 w-8 text-primary" />
|
|
</div>
|
|
<CardTitle>Profil inaccessible</CardTitle>
|
|
<CardDescription>
|
|
Vous devez être connecté pour voir votre profil, vos mèmes et vos
|
|
favoris.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Button asChild className="w-full sm:w-auto">
|
|
<Link href="/login">Se connecter</Link>
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</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">
|
|
<div className="relative group">
|
|
<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>
|
|
<button
|
|
type="button"
|
|
onClick={handleAvatarClick}
|
|
className="absolute inset-0 flex items-center justify-center bg-black/40 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
|
|
>
|
|
<Camera className="h-8 w-8" />
|
|
</button>
|
|
<input
|
|
type="file"
|
|
ref={fileInputRef}
|
|
onChange={handleFileChange}
|
|
accept="image/*"
|
|
className="hidden"
|
|
/>
|
|
</div>
|
|
<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">{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 className="flex flex-wrap justify-center md:justify-start gap-2">
|
|
<Button asChild variant="outline" size="sm">
|
|
<Link href="/settings">
|
|
<Settings className="h-4 w-4 mr-2" />
|
|
Paramètres
|
|
</Link>
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => logout()}
|
|
className="text-red-500 hover:text-red-600 hover:bg-red-50"
|
|
>
|
|
<LogOut className="h-4 w-4 mr-2" />
|
|
Déconnexion
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Tabs value={tab} className="w-full">
|
|
<TabsList className="grid w-full grid-cols-2 mb-8">
|
|
<TabsTrigger value="memes" asChild>
|
|
<Link href="/profile?tab=memes">Mes Mèmes</Link>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="favorites" asChild>
|
|
<Link href="/profile?tab=favorites">Mes Favoris</Link>
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="memes">
|
|
<ContentList fetchFn={fetchMyMemes} />
|
|
</TabsContent>
|
|
<TabsContent value="favorites">
|
|
<ContentList fetchFn={fetchMyFavorites} />
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|