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:
116
frontend/src/components/notification-handler.tsx
Normal file
116
frontend/src/components/notification-handler.tsx
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user