From b3ff2a59beef8739a41f9a8a61a78ab001655218 Mon Sep 17 00:00:00 2001
From: Nicolas Temciuc <nicotemciuc@gmail.com>
Date: Thu, 14 Nov 2024 11:59:39 -0300
Subject: [PATCH 1/6] build(deps): add `switch` component

---
 front-office/package.json   |  1 +
 front-office/pnpm-lock.yaml | 31 +++++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/front-office/package.json b/front-office/package.json
index 1890603..a99dc61 100644
--- a/front-office/package.json
+++ b/front-office/package.json
@@ -17,6 +17,7 @@
     "@radix-ui/react-label": "^2.1.0",
     "@radix-ui/react-select": "^2.1.2",
     "@radix-ui/react-slot": "^1.1.0",
+    "@radix-ui/react-switch": "^1.1.1",
     "@radix-ui/react-toast": "^1.2.2",
     "axios": "^1.7.7",
     "class-variance-authority": "^0.7.0",
diff --git a/front-office/pnpm-lock.yaml b/front-office/pnpm-lock.yaml
index 9d88fce..f7554e2 100644
--- a/front-office/pnpm-lock.yaml
+++ b/front-office/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
       '@radix-ui/react-slot':
         specifier: ^1.1.0
         version: 1.1.0(@types/react@18.3.12)(react@19.0.0-rc-02c0e824-20241028)
+      '@radix-ui/react-switch':
+        specifier: ^1.1.1
+        version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028))(react@19.0.0-rc-02c0e824-20241028)
       '@radix-ui/react-toast':
         specifier: ^1.2.2
         version: 1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028))(react@19.0.0-rc-02c0e824-20241028)
@@ -641,6 +644,19 @@ packages:
       '@types/react':
         optional: true
 
+  '@radix-ui/react-switch@1.1.1':
+    resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
   '@radix-ui/react-toast@1.2.2':
     resolution: {integrity: sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==}
     peerDependencies:
@@ -2724,6 +2740,21 @@ snapshots:
     optionalDependencies:
       '@types/react': 18.3.12
 
+  '@radix-ui/react-switch@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028))(react@19.0.0-rc-02c0e824-20241028)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.0
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@19.0.0-rc-02c0e824-20241028)
+      '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@19.0.0-rc-02c0e824-20241028)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028))(react@19.0.0-rc-02c0e824-20241028)
+      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@19.0.0-rc-02c0e824-20241028)
+      '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.12)(react@19.0.0-rc-02c0e824-20241028)
+      '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.12)(react@19.0.0-rc-02c0e824-20241028)
+      react: 19.0.0-rc-02c0e824-20241028
+      react-dom: 19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028)
+    optionalDependencies:
+      '@types/react': 18.3.12
+      '@types/react-dom': 18.3.1
+
   '@radix-ui/react-toast@1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028))(react@19.0.0-rc-02c0e824-20241028)':
     dependencies:
       '@radix-ui/primitive': 1.1.0
-- 
GitLab


From 8784d7cb74eddfa177ed7be13c2eb0737adfe790 Mon Sep 17 00:00:00 2001
From: Nicolas Temciuc <nicotemciuc@gmail.com>
Date: Thu, 14 Nov 2024 12:00:20 -0300
Subject: [PATCH 2/6] refactor: remove categories and search bar from navbar

---
 front-office/src/components/navbar.tsx | 44 --------------------------
 1 file changed, 44 deletions(-)

diff --git a/front-office/src/components/navbar.tsx b/front-office/src/components/navbar.tsx
index aa0ea6b..57b8c14 100644
--- a/front-office/src/components/navbar.tsx
+++ b/front-office/src/components/navbar.tsx
@@ -13,51 +13,7 @@ const Navbar = () => {
         <span className="text-lg font-semibold">Verificando.uy</span>
       </Link>
       <nav className="hidden items-center gap-4 md:flex">
-        <Link href="#" className="hover:underline" >
-          Home
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Politica
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Economía
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Sociedad
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Ciencia
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Tecnología
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Deporte
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Cultura
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Salud
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Ambiente
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Policiales
-        </Link>
-        <Link href="#" className="hover:underline" >
-          Otros
-        </Link>
       </nav>
-      <div className="relative">
-        <SearchIcon className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-500" />
-        <Input
-          type="search"
-          placeholder="Buscar noticias..."
-          className="w-full rounded-md bg-gray-800 px-8 py-2 text-sm focus:outline-none"
-        />
-      </div>
       <UserMenu />
     </header>
   );
-- 
GitLab


From 91aebe28443b84b3e4171351efaa11b7f2c73ccf Mon Sep 17 00:00:00 2001
From: Nicolas Temciuc <nicotemciuc@gmail.com>
Date: Thu, 14 Nov 2024 12:00:52 -0300
Subject: [PATCH 3/6] chore: add `switch` component

---
 front-office/src/components/ui/switch.tsx | 29 +++++++++++++++++++++++
 1 file changed, 29 insertions(+)
 create mode 100644 front-office/src/components/ui/switch.tsx

diff --git a/front-office/src/components/ui/switch.tsx b/front-office/src/components/ui/switch.tsx
new file mode 100644
index 0000000..5f4117f
--- /dev/null
+++ b/front-office/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef<
+  React.ElementRef<typeof SwitchPrimitives.Root>,
+  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
+>(({ className, ...props }, ref) => (
+  <SwitchPrimitives.Root
+    className={cn(
+      "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+      className
+    )}
+    {...props}
+    ref={ref}
+  >
+    <SwitchPrimitives.Thumb
+      className={cn(
+        "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
+      )}
+    />
+  </SwitchPrimitives.Root>
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }
-- 
GitLab


From a2ebe393b9692e34f6b3f727e36fcaccbaaa9cff Mon Sep 17 00:00:00 2001
From: Nicolas Temciuc <nicotemciuc@gmail.com>
Date: Thu, 14 Nov 2024 12:01:25 -0300
Subject: [PATCH 4/6] chore: add manage my own notifications

---
 .../components/custom/manage-notification.tsx | 105 +++++++++++++++
 .../components/custom/notification-button.tsx | 124 ++++++++++++++++++
 .../src/components/custom/user-menu.tsx       |  30 ++++-
 3 files changed, 256 insertions(+), 3 deletions(-)
 create mode 100644 front-office/src/components/custom/manage-notification.tsx
 create mode 100644 front-office/src/components/custom/notification-button.tsx

diff --git a/front-office/src/components/custom/manage-notification.tsx b/front-office/src/components/custom/manage-notification.tsx
new file mode 100644
index 0000000..d84465f
--- /dev/null
+++ b/front-office/src/components/custom/manage-notification.tsx
@@ -0,0 +1,105 @@
+import React, { useEffect, useState } from "react";
+import Cookies from "js-cookie";
+import axiosInstance from "@/utils/axios-instance";
+import { Switch } from "@/components/ui/switch";
+import { jwtDecode } from "jwt-decode";
+
+const ManageNotification = ({ setIsNotificationOpen }) => {
+  const [categories, setCategories] = useState([]);
+  const [subscriptions, setSubscriptions] = useState(new Set());
+  const [loadingCategories, setLoadingCategories] = useState(true);
+  const [loadingSubscriptions, setLoadingSubscriptions] = useState(true);
+
+  useEffect(() => {
+    async function fetchCategoriesAndSubscriptions() {
+      try {
+        const token = Cookies.get('token');
+
+        // Fetch all categories
+        const categoriesResponse = await axiosInstance.get(`/facts/categories`, {
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+        setCategories(categoriesResponse);
+        setLoadingCategories(false);
+
+        const userId = getUserIdFromToken(token); // Replace with actual extraction logic
+        const subscriptionsResponse = await axiosInstance.get(`/subscriptions/${userId}`, {
+          headers: {
+            Authorization: `Bearer ${token}`,
+          },
+        });
+        console.log("subscriptionsResponse", subscriptionsResponse);
+
+        const subscribedCategories = new Set(subscriptionsResponse.map((sub) => sub.category));
+        setSubscriptions(subscribedCategories);
+        setLoadingSubscriptions(false);
+
+      } catch (error) {
+        console.error("Failed to fetch categories or subscriptions:", error);
+        setLoadingCategories(true);
+        setLoadingSubscriptions(true);
+      }
+    }
+
+    fetchCategoriesAndSubscriptions();
+  }, []);
+
+  const handleToggleSubscription = async (category) => {
+    const token = Cookies.get('token');
+    const userId = getUserIdFromToken(token);
+
+    if (subscriptions.has(category)) {
+      await axiosInstance.delete(`/subscriptions/${userId}/${category}`, {
+        headers: {
+          Authorization: `Bearer ${token}`,
+        },
+      });
+      setSubscriptions((prev) => {
+        const newSubscriptions = new Set(prev);
+        newSubscriptions.delete(category);
+        return newSubscriptions;
+      });
+    } else {
+      await axiosInstance.post(`/subscriptions`, {
+        userId,
+        category,
+      }, {
+        headers: {
+          Authorization: `Bearer ${token}`,
+        },
+      });
+      setSubscriptions((prev) => new Set(prev).add(category));
+    }
+  };
+
+  if (loadingCategories || loadingSubscriptions) {
+    return <div>Loading...</div>;
+  }
+
+  return (
+    <div className="max-w-full grid grid-cols-2 gap-4">
+      {categories.map((category) => (
+        <div key={category} className="flex items-center justify-between rounded-lg border p-4 my-4">
+          <div className="space-y-0.5">
+            <div className="font-semibold">{category}</div>
+          </div>
+          <div>
+            <Switch
+              checked={subscriptions.has(category)}
+              onClick={() => handleToggleSubscription(category)}
+            />
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+};
+
+function getUserIdFromToken(token) {
+  const { id } = jwtDecode(token);
+  return id;
+}
+
+export default ManageNotification;
diff --git a/front-office/src/components/custom/notification-button.tsx b/front-office/src/components/custom/notification-button.tsx
new file mode 100644
index 0000000..6687d31
--- /dev/null
+++ b/front-office/src/components/custom/notification-button.tsx
@@ -0,0 +1,124 @@
+import { useEffect, useState } from 'react';
+import { Bell, X } from 'lucide-react';
+import { jwtDecode } from "jwt-decode";
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuLabel,
+  DropdownMenuSeparator,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import axiosInstance from '@/utils/axios-instance';
+import Cookies from 'js-cookie';
+
+const NotificationsDropdown = () => {
+  const [notifications, setNotifications] = useState([]);
+  const [subscriptions, setSubscriptions] = useState([]);
+  const [webSocket, setWebSocket] = useState(null);
+
+  const token = Cookies.get('token');
+  const { id } = jwtDecode(token);
+  const userId = id;
+
+  const loadSubscriptions = async () => {
+    try {
+      const response = await axiosInstance.get(`/subscriptions/${userId}`, {
+        headers: { Authorization: `Bearer ${token}` }
+      });
+      setSubscriptions(response.data);
+    } catch (error) {
+      console.error('Error loading subscriptions:', error);
+    }
+  };
+
+  // Function to unsubscribe from a category
+  const unsubscribeFromCategory = async (category) => {
+    try {
+      await axiosInstance.delete(`/subscriptions/${userId}/${category}`, {
+        headers: { Authorization: `Bearer ${token}` }
+      });
+      loadSubscriptions();
+    } catch (error) {
+      console.error('Error unsubscribing from category:', error);
+    }
+  };
+
+  // WebSocket setup for receiving notifications
+  useEffect(() => {
+    const socket = new WebSocket(`ws://localhost:8080/notifications/${userId}`);
+    setWebSocket(socket);
+
+    socket.onmessage = (event) => {
+      const data = JSON.parse(event.data);
+      setNotifications((prev) => [{ message: data.notification, read: false }, ...prev]);
+    };
+
+    socket.onopen = () => {
+      console.log('WebSocket connected');
+      // Heartbeat to keep the connection alive every 40 seconds
+      const heartbeat = setInterval(() => {
+        socket.send(JSON.stringify({ type: 'heartbeat' }));
+      }, 40000);
+
+      // Clear interval on WebSocket close
+      socket.onclose = () => clearInterval(heartbeat);
+    };
+
+    return () => socket.close(); // Cleanup on component unmount
+  }, [userId]);
+
+  // Clear a single notification
+  const clearNotification = (index) => {
+    setNotifications(notifications.filter((_, i) => i !== index));
+  };
+
+  // Clear all notifications
+  const clearAllNotifications = () => setNotifications([]);
+
+  return (
+    <DropdownMenu>
+      <DropdownMenuTrigger className="flex items-center p-2 text-gray-500 hover:text-gray-800 focus:outline-none">
+        <Bell className="w-6 h-6" />
+      </DropdownMenuTrigger>
+      <DropdownMenuContent className="w-64 p-2 bg-white border rounded-md shadow-lg">
+        {notifications.length > 0 ? (
+          notifications.map((notification, index) => (
+            <DropdownMenuItem
+              key={index}
+              className={`flex items-center justify-between p-2 rounded-md ${
+                notification.read ? 'text-gray-400' : 'text-black font-semibold'
+              }`}
+            >
+              <span>{notification.message}</span>
+              <button
+                onClick={(e) => {
+                  e.stopPropagation();
+                  clearNotification(index);
+                }}
+                className="p-1 text-gray-400 hover:text-red-500"
+              >
+                <X className="w-4 h-4" />
+              </button>
+            </DropdownMenuItem>
+          ))
+        ) : (
+          <p className="p-2 text-gray-500 text-center">No new notifications</p>
+        )}
+        {notifications.length > 0 && (
+          <>
+            <DropdownMenuSeparator className="my-2" />
+            <DropdownMenuItem
+              onSelect={clearAllNotifications}
+              className="p-2 text-red-500 cursor-pointer hover:bg-red-50 rounded-md"
+            >
+              Clear All Notifications
+            </DropdownMenuItem>
+          </>
+        )}
+      </DropdownMenuContent>
+    </DropdownMenu>
+  );
+};
+
+export default NotificationsDropdown;
diff --git a/front-office/src/components/custom/user-menu.tsx b/front-office/src/components/custom/user-menu.tsx
index 7339f0b..8f1c4ba 100644
--- a/front-office/src/components/custom/user-menu.tsx
+++ b/front-office/src/components/custom/user-menu.tsx
@@ -4,9 +4,11 @@ import Cookies from "js-cookie";
 import { useEffect, useState } from "react";
 import { jwtDecode } from "jwt-decode";
 import LoginButton from "./login-button";
-import { LogOut } from "lucide-react";
+import { LogOut, Bolt } from "lucide-react";
 import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
 import DonateButton from '@/components/custom/donate-button'
+import NotificationButton from '@/components/custom/notification-button'
+import ManageNotification from '@/components/custom/manage-notification'
 import { Button } from '@/components/ui/button'
 import {
   Dialog,
@@ -34,6 +36,7 @@ import { useRouter } from "next/navigation";
 const UserMenu = () => {
   const [user, setUser] = useState(null);
   const [isOpen, setIsOpen] = useState(false);
+  const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
 
   const logout = () => {
     Cookies.remove("token");
@@ -49,10 +52,11 @@ const UserMenu = () => {
   }, []);
 
   return (
-    <>
+    <div className="flex items-center space-x-4">
       {user ? (
         <>
           <DonateButton />
+          <NotificationButton />
           <Dialog open={isOpen} onOpenChange={setIsOpen}> {}
             <DialogContent>
               <DialogHeader>
@@ -64,6 +68,17 @@ const UserMenu = () => {
               <FactForm setIsOpen={setIsOpen} /> {}
             </DialogContent>
           </Dialog>
+          <Dialog open={isNotificationsOpen} onOpenChange={setIsNotificationsOpen}>
+            <DialogContent>
+              <DialogHeader>
+                <DialogTitle>Manage Notifications</DialogTitle>
+                <DialogDescription>
+                  You can manage your notifications here.
+                </DialogDescription>
+              </DialogHeader>
+              <ManageNotification setIsOpen={setIsNotificationsOpen} />
+            </DialogContent>
+          </Dialog>
 
           <DropdownMenu>
             <DropdownMenuTrigger asChild>
@@ -92,6 +107,15 @@ const UserMenu = () => {
                   Suggest a Fact
                 </button>
               </DropdownMenuItem>
+              <DropdownMenuItem>
+                <button
+                  onClick={() => setIsNotificationsOpen(true)}
+                  className="flex justify-start rounded-md transition-all duration-75 hover:bg-neutral-100"
+                >
+                  <Bolt size={24} className="mr-2" />
+                  Manage Notifications
+                </button>
+              </DropdownMenuItem>
               <DropdownMenuItem onClick={logout}>
                 <button className="flex justify-start rounded-md transition-all duration-75 hover:bg-neutral-100 text-red-500 hover:text-red-900">
                   <LogOut size={24} className="mr-2" />
@@ -104,7 +128,7 @@ const UserMenu = () => {
       ) : (
         <LoginButton />
       )}
-    </>
+    </div>
   );
 }
 
-- 
GitLab


From 3ebde4829566df2c30d3eb069e32e0afc568c0c5 Mon Sep 17 00:00:00 2001
From: Nicolas Temciuc <nicotemciuc@gmail.com>
Date: Thu, 14 Nov 2024 12:22:34 -0300
Subject: [PATCH 5/6] chore: notifications works

---
 .../components/custom/notification-button.tsx | 26 ++-----------------
 1 file changed, 2 insertions(+), 24 deletions(-)

diff --git a/front-office/src/components/custom/notification-button.tsx b/front-office/src/components/custom/notification-button.tsx
index 6687d31..a45c960 100644
--- a/front-office/src/components/custom/notification-button.tsx
+++ b/front-office/src/components/custom/notification-button.tsx
@@ -1,5 +1,5 @@
 import { useEffect, useState } from 'react';
-import { Bell, X } from 'lucide-react';
+import { Bell, BellRing, X } from 'lucide-react';
 import { jwtDecode } from "jwt-decode";
 import {
   DropdownMenu,
@@ -14,35 +14,12 @@ import Cookies from 'js-cookie';
 
 const NotificationsDropdown = () => {
   const [notifications, setNotifications] = useState([]);
-  const [subscriptions, setSubscriptions] = useState([]);
   const [webSocket, setWebSocket] = useState(null);
 
   const token = Cookies.get('token');
   const { id } = jwtDecode(token);
   const userId = id;
 
-  const loadSubscriptions = async () => {
-    try {
-      const response = await axiosInstance.get(`/subscriptions/${userId}`, {
-        headers: { Authorization: `Bearer ${token}` }
-      });
-      setSubscriptions(response.data);
-    } catch (error) {
-      console.error('Error loading subscriptions:', error);
-    }
-  };
-
-  // Function to unsubscribe from a category
-  const unsubscribeFromCategory = async (category) => {
-    try {
-      await axiosInstance.delete(`/subscriptions/${userId}/${category}`, {
-        headers: { Authorization: `Bearer ${token}` }
-      });
-      loadSubscriptions();
-    } catch (error) {
-      console.error('Error unsubscribing from category:', error);
-    }
-  };
 
   // WebSocket setup for receiving notifications
   useEffect(() => {
@@ -51,6 +28,7 @@ const NotificationsDropdown = () => {
 
     socket.onmessage = (event) => {
       const data = JSON.parse(event.data);
+      console.log('Received notification:', data);
       setNotifications((prev) => [{ message: data.notification, read: false }, ...prev]);
     };
 
-- 
GitLab


From d9877553caf7db65c123a152281e4572ccd6e268 Mon Sep 17 00:00:00 2001
From: Nicolas Temciuc <nicotemciuc@gmail.com>
Date: Thu, 14 Nov 2024 12:25:39 -0300
Subject: [PATCH 6/6] chore: change icon when new notification

---
 .../components/custom/notification-button.tsx | 22 ++++++++++++++-----
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/front-office/src/components/custom/notification-button.tsx b/front-office/src/components/custom/notification-button.tsx
index a45c960..ac06d97 100644
--- a/front-office/src/components/custom/notification-button.tsx
+++ b/front-office/src/components/custom/notification-button.tsx
@@ -8,7 +8,7 @@ import {
   DropdownMenuLabel,
   DropdownMenuSeparator,
   DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
+} from "@/components/ui/dropdown-menu";
 import axiosInstance from '@/utils/axios-instance';
 import Cookies from 'js-cookie';
 
@@ -20,7 +20,6 @@ const NotificationsDropdown = () => {
   const { id } = jwtDecode(token);
   const userId = id;
 
-
   // WebSocket setup for receiving notifications
   useEffect(() => {
     const socket = new WebSocket(`ws://localhost:8080/notifications/${userId}`);
@@ -28,12 +27,11 @@ const NotificationsDropdown = () => {
 
     socket.onmessage = (event) => {
       const data = JSON.parse(event.data);
-      console.log('Received notification:', data);
+      if (data.notification.includes('Connection established')) return;
       setNotifications((prev) => [{ message: data.notification, read: false }, ...prev]);
     };
 
     socket.onopen = () => {
-      console.log('WebSocket connected');
       // Heartbeat to keep the connection alive every 40 seconds
       const heartbeat = setInterval(() => {
         socket.send(JSON.stringify({ type: 'heartbeat' }));
@@ -54,10 +52,22 @@ const NotificationsDropdown = () => {
   // Clear all notifications
   const clearAllNotifications = () => setNotifications([]);
 
+  // Count unread notifications
+  const unreadCount = notifications.filter((notification) => !notification.read).length;
+
   return (
     <DropdownMenu>
-      <DropdownMenuTrigger className="flex items-center p-2 text-gray-500 hover:text-gray-800 focus:outline-none">
-        <Bell className="w-6 h-6" />
+      <DropdownMenuTrigger className="relative flex items-center p-2 text-gray-500 hover:text-gray-800 focus:outline-none">
+        {unreadCount > 0 ? (
+          <BellRing className="w-6 h-6 text-red-500" />
+        ) : (
+          <Bell className="w-6 h-6" />
+        )}
+        {unreadCount > 0 && (
+          <span className="absolute top-0 right-0 flex items-center justify-center w-4 h-4 text-xs font-semibold text-white bg-red-500 rounded-full">
+            {unreadCount}
+          </span>
+        )}
       </DropdownMenuTrigger>
       <DropdownMenuContent className="w-64 p-2 bg-white border rounded-md shadow-lg">
         {notifications.length > 0 ? (
-- 
GitLab