feat(contents): add author actions to content card

- Added dropdown menu for authors to edit or delete their content.
- Integrated `UserContentEditDialog` for inline editing.
- Enabled content deletion with confirmation and success/error feedback.
- Improved UI with `DropdownMenu` for better action accessibility.
This commit is contained in:
Mathis HERRIOT
2026-01-21 13:42:11 +01:00
parent b968d1e6f8
commit 8778508ced

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { Eye, Heart, MoreHorizontal, Share2 } from "lucide-react"; import { Edit, Eye, Heart, MoreHorizontal, Share2, Trash2 } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
@@ -15,20 +15,31 @@ import {
CardFooter, CardFooter,
CardHeader, CardHeader,
} from "@/components/ui/card"; } from "@/components/ui/card";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useAuth } from "@/providers/auth-provider"; import { useAuth } from "@/providers/auth-provider";
import { ContentService } from "@/services/content.service"; import { ContentService } from "@/services/content.service";
import { FavoriteService } from "@/services/favorite.service"; import { FavoriteService } from "@/services/favorite.service";
import type { Content } from "@/types/content"; import type { Content } from "@/types/content";
import { UserContentEditDialog } from "./user-content-edit-dialog";
interface ContentCardProps { interface ContentCardProps {
content: Content; content: Content;
onUpdate?: () => void;
} }
export function ContentCard({ content }: ContentCardProps) { export function ContentCard({ content, onUpdate }: ContentCardProps) {
const { isAuthenticated } = useAuth(); const { isAuthenticated, user } = useAuth();
const router = useRouter(); const router = useRouter();
const [isLiked, setIsLiked] = React.useState(content.isLiked || false); const [isLiked, setIsLiked] = React.useState(content.isLiked || false);
const [likesCount, setLikesCount] = React.useState(content.favoritesCount); const [likesCount, setLikesCount] = React.useState(content.favoritesCount);
const [editDialogOpen, setEditDialogOpen] = React.useState(false);
const isAuthor = user?.uuid === content.authorId;
React.useEffect(() => { React.useEffect(() => {
setIsLiked(content.isLiked || false); setIsLiked(content.isLiked || false);
@@ -71,28 +82,69 @@ export function ContentCard({ content }: ContentCardProps) {
} }
}; };
const handleDelete = async () => {
if (!confirm("Êtes-vous sûr de vouloir supprimer ce mème ?")) return;
try {
await ContentService.remove(content.id);
toast.success("Mème supprimé !");
onUpdate?.();
} catch (_error) {
toast.error("Erreur lors de la suppression.");
}
};
return ( return (
<Card className="overflow-hidden border-none shadow-sm hover:shadow-md transition-shadow"> <>
<CardHeader className="p-4 flex flex-row items-center space-y-0 gap-3"> <Card className="overflow-hidden border-none shadow-sm hover:shadow-md transition-shadow">
<Avatar className="h-8 w-8"> <CardHeader className="p-4 flex flex-row items-center space-y-0 gap-3">
<AvatarImage src={content.author.avatarUrl} /> <Avatar className="h-8 w-8">
<AvatarFallback>{content.author.username[0].toUpperCase()}</AvatarFallback> <AvatarImage src={content.author.avatarUrl} />
</Avatar> <AvatarFallback>{content.author.username[0].toUpperCase()}</AvatarFallback>
<div className="flex flex-col"> </Avatar>
<Link <div className="flex flex-col">
href={`/user/${content.author.username}`} <Link
className="text-sm font-semibold hover:underline" href={`/user/${content.author.username}`}
> className="text-sm font-semibold hover:underline"
{content.author.displayName || content.author.username} >
</Link> {content.author.displayName || content.author.username}
<span className="text-xs text-muted-foreground"> </Link>
{new Date(content.createdAt).toLocaleDateString("fr-FR")} <span className="text-xs text-muted-foreground">
</span> {new Date(content.createdAt).toLocaleDateString("fr-FR")}
</div> </span>
<Button variant="ghost" size="icon" className="ml-auto h-8 w-8"> </div>
<MoreHorizontal className="h-4 w-4" />
</Button> <div className="ml-auto flex items-center gap-1">
</CardHeader> <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{isAuthor && (
<>
<DropdownMenuItem onClick={() => setEditDialogOpen(true)}>
<Edit className="h-4 w-4 mr-2" />
Modifier
</DropdownMenuItem>
<DropdownMenuItem
onClick={handleDelete}
className="text-destructive focus:text-destructive"
>
<Trash2 className="h-4 w-4 mr-2" />
Supprimer
</DropdownMenuItem>
</>
)}
<DropdownMenuItem onClick={() => toast.success("Lien copié !")}>
<Share2 className="h-4 w-4 mr-2" />
Partager
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</CardHeader>
<CardContent className="p-0 relative bg-zinc-200 dark:bg-zinc-900 aspect-square flex items-center justify-center"> <CardContent className="p-0 relative bg-zinc-200 dark:bg-zinc-900 aspect-square flex items-center justify-center">
<Link href={`/meme/${content.slug}`} className="w-full h-full relative"> <Link href={`/meme/${content.slug}`} className="w-full h-full relative">
{content.mimeType.startsWith("image/") ? ( {content.mimeType.startsWith("image/") ? (
@@ -161,5 +213,12 @@ export function ContentCard({ content }: ContentCardProps) {
</div> </div>
</CardFooter> </CardFooter>
</Card> </Card>
<UserContentEditDialog
content={content}
open={editDialogOpen}
onOpenChange={setEditDialogOpen}
onSuccess={() => onUpdate?.()}
/>
</>
); );
} }