feat: implement NotificationHandler component for real-time notifications

- Added `NotificationHandler` component for managing real-time notifications using sockets.
- Display notifications for comments, replies, likes, and messages with interactive toasts.
- Integrated click handling for navigation to relevant pages based on notification type.
This commit is contained in:
Mathis HERRIOT
2026-01-29 16:56:19 +01:00
parent c83ba6eb7d
commit 62bf03d07a

View File

@@ -0,0 +1,116 @@
"use client";
import { Bell, Heart, MessageCircle, Reply } from "lucide-react";
import { useRouter } from "next/navigation";
import * as React from "react";
import { toast } from "sonner";
import { useSocket } from "@/providers/socket-provider";
interface NotificationData {
type: "comment" | "reply" | "like_comment" | "message";
userId: string;
username: string;
contentId?: string;
commentId?: string;
text: string;
}
export function NotificationHandler() {
const { socket } = useSocket();
const router = useRouter();
React.useEffect(() => {
if (!socket) return;
const handleNotification = (data: NotificationData) => {
// Ne pas afficher de toast si on est déjà sur la page des messages pour un nouveau message
if (data.type === "message" && window.location.pathname === "/messages") {
return;
}
toast.custom(
(t) => (
<div
role="button"
tabIndex={0}
className="flex items-start gap-3 bg-white dark:bg-zinc-900 p-4 rounded-xl shadow-lg border border-zinc-200 dark:border-zinc-800 w-full max-w-sm cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors"
onClick={() => {
toast.dismiss(t);
if (data.type === "message") {
router.push("/messages");
} else if (data.contentId) {
router.push(`/meme/${data.contentId}`);
}
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
toast.dismiss(t);
if (data.type === "message") {
router.push("/messages");
} else if (data.contentId) {
router.push(`/meme/${data.contentId}`);
}
}
}}
>
<div className="bg-primary/10 p-2 rounded-full shrink-0">
{data.type === "comment" && (
<MessageCircle className="h-4 w-4 text-primary" />
)}
{data.type === "reply" && <Reply className="h-4 w-4 text-primary" />}
{data.type === "like_comment" && (
<Heart className="h-4 w-4 text-red-500" />
)}
{data.type === "message" && (
<MessageCircle className="h-4 w-4 text-primary" />
)}
</div>
<div className="flex-1 overflow-hidden">
<p className="text-sm font-bold">@{data.username}</p>
<p className="text-xs text-muted-foreground truncate">{data.text}</p>
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
toast.dismiss(t);
}}
className="text-muted-foreground hover:text-foreground"
>
<Bell className="h-3 w-3" />
</button>
</div>
),
{
duration: 5000,
position: "top-right",
},
);
};
socket.on("notification", handleNotification);
// Aussi pour les nouveaux messages (si on veut un toast global)
socket.on("new_message", (data: { message: any }) => {
if (window.location.pathname !== "/messages") {
toast(
`Nouveau message de @${data.message.sender?.username || "un membre"}`,
{
description: data.message.text.substring(0, 50),
action: {
label: "Voir",
onClick: () => router.push("/messages"),
},
},
);
}
});
return () => {
socket.off("notification");
socket.off("new_message");
};
}, [socket, router]);
return null;
}