Files
ember-market-frontend/components/notifications/UnifiedNotifications.tsx
g fe01f31538
Some checks failed
Build Frontend / build (push) Failing after 7s
Refactor UI imports and update component paths
Replaces imports from 'components/ui' with 'components/common' across the app and dashboard pages, and updates model and API imports to use new paths under 'lib'. Removes redundant authentication checks from several dashboard pages. Adds new dashboard components and utility files, and reorganizes hooks and services into the 'lib' directory for improved structure.
2026-01-13 05:02:13 +00:00

287 lines
12 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { Badge } from "@/components/common/badge";
import { Button } from "@/components/common/button";
import { BellRing, Package, MessageCircle } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/common/dropdown-menu";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/common/tabs";
import { useNotifications } from "@/lib/notification-context";
export default function UnifiedNotifications() {
const router = useRouter();
const [activeTab, setActiveTab] = useState<string>("all");
// Get notification state from context
const {
unreadCounts,
chatMetadata,
newOrders,
clearOrderNotifications,
totalNotifications,
loading,
} = useNotifications();
// Navigation handlers
const handleChatClick = (chatId: string) => {
router.push(`/dashboard/chats/${chatId}`);
};
const handleOrderClick = (orderId: string) => {
router.push(`/dashboard/orders/${orderId}`);
};
// Format the price as currency
const formatPrice = (price: number) => {
return `£${price.toFixed(2)}`;
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative" disabled={loading}>
<BellRing className="h-5 w-5" />
{totalNotifications > 0 && (
<Badge
variant="destructive"
className="absolute -top-1 -right-1 px-1.5 py-0.5 text-xs"
>
{totalNotifications}
</Badge>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-80" collisionPadding={10}>
<div className="p-2 border-b">
<Tabs defaultValue="all" value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="all" className="text-xs">
All
{totalNotifications > 0 && (
<Badge variant="secondary" className="ml-1">
{totalNotifications}
</Badge>
)}
</TabsTrigger>
<TabsTrigger value="messages" className="text-xs">
Messages
{unreadCounts.totalUnread > 0 && (
<Badge variant="secondary" className="ml-1">
{unreadCounts.totalUnread}
</Badge>
)}
</TabsTrigger>
<TabsTrigger value="orders" className="text-xs">
Orders
{newOrders.length > 0 && (
<Badge variant="secondary" className="ml-1">
{newOrders.length}
</Badge>
)}
</TabsTrigger>
</TabsList>
<TabsContent value="all" className="m-0">
{totalNotifications === 0 ? (
<div className="p-4 flex items-center justify-center">
<p className="text-sm text-muted-foreground">No new notifications</p>
</div>
) : (
<div className="max-h-96 overflow-y-auto">
{/* Messages Section */}
{unreadCounts.totalUnread > 0 && (
<>
<div className="px-3 py-2 text-xs font-medium bg-muted/50">
Unread Messages
</div>
{Object.entries(unreadCounts.chatCounts).slice(0, 3).map(([chatId, count]) => (
<DropdownMenuItem
key={`chat-${chatId}`}
className="p-3 cursor-pointer"
onClick={() => handleChatClick(chatId)}
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2">
<MessageCircle className="h-4 w-4 text-blue-500" />
<div>
<p className="font-medium">
Customer {chatMetadata[chatId]?.buyerId.slice(-4) || 'Unknown'}
</p>
<p className="text-xs text-muted-foreground">
{count} new {count === 1 ? 'message' : 'messages'}
</p>
</div>
</div>
<Badge variant="secondary">{count}</Badge>
</div>
</DropdownMenuItem>
))}
{Object.keys(unreadCounts.chatCounts).length > 3 && (
<div className="px-3 py-2 text-xs text-center text-muted-foreground">
+ {Object.keys(unreadCounts.chatCounts).length - 3} more unread chats
</div>
)}
</>
)}
{/* Orders Section */}
{newOrders.length > 0 && (
<>
<div className="px-3 py-2 text-xs font-medium bg-muted/50 flex justify-between items-center">
<span>New Paid Orders</span>
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
clearOrderNotifications();
}}
className="h-6 text-xs"
>
Clear
</Button>
</div>
{newOrders.slice(0, 3).map((order) => (
<DropdownMenuItem
key={`order-${order._id}`}
className="p-3 cursor-pointer"
onClick={() => handleOrderClick(order._id)}
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2">
<Package className="h-4 w-4 text-green-500" />
<div>
<p className="font-medium">Order #{order.orderId}</p>
<p className="text-xs text-muted-foreground">
{formatPrice(order.totalPrice)}
</p>
</div>
</div>
<Badge className="bg-green-500 hover:bg-green-600">Paid</Badge>
</div>
</DropdownMenuItem>
))}
{newOrders.length > 3 && (
<div className="px-3 py-2 text-xs text-center text-muted-foreground">
+ {newOrders.length - 3} more new orders
</div>
)}
</>
)}
</div>
)}
</TabsContent>
<TabsContent value="messages" className="m-0">
{unreadCounts.totalUnread === 0 ? (
<div className="p-4 flex items-center justify-center">
<p className="text-sm text-muted-foreground">No unread messages</p>
</div>
) : (
<>
<div className="max-h-96 overflow-y-auto">
{Object.entries(unreadCounts.chatCounts).map(([chatId, count]) => (
<DropdownMenuItem
key={`chat-tab-${chatId}`}
className="p-3 cursor-pointer"
onClick={() => handleChatClick(chatId)}
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2">
<MessageCircle className="h-4 w-4 text-blue-500" />
<div>
<p className="font-medium">
Customer {chatMetadata[chatId]?.buyerId.slice(-4) || 'Unknown'}
</p>
<p className="text-xs text-muted-foreground">
{count} new {count === 1 ? 'message' : 'messages'}
</p>
</div>
</div>
<Badge variant="secondary">{count}</Badge>
</div>
</DropdownMenuItem>
))}
</div>
<div className="p-2 border-t">
<Button
variant="outline"
className="w-full"
onClick={() => router.push('/dashboard/chats')}
>
View All Chats
</Button>
</div>
</>
)}
</TabsContent>
<TabsContent value="orders" className="m-0">
{newOrders.length === 0 ? (
<div className="p-4 flex items-center justify-center">
<p className="text-sm text-muted-foreground">No new paid orders</p>
</div>
) : (
<>
<div className="px-3 py-2 text-xs font-medium bg-muted/50 flex justify-between items-center">
<span>New Paid Orders</span>
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
clearOrderNotifications();
}}
className="h-6 text-xs"
>
Clear
</Button>
</div>
<div className="max-h-96 overflow-y-auto">
{newOrders.map((order) => (
<DropdownMenuItem
key={`order-tab-${order._id}`}
className="p-3 cursor-pointer"
onClick={() => handleOrderClick(order._id)}
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2">
<Package className="h-4 w-4 text-green-500" />
<div>
<p className="font-medium">Order #{order.orderId}</p>
<p className="text-xs text-muted-foreground">
{formatPrice(order.totalPrice)}
</p>
</div>
</div>
<Badge className="bg-green-500 hover:bg-green-600">Paid</Badge>
</div>
</DropdownMenuItem>
))}
</div>
<div className="p-2 border-t">
<Button
variant="outline"
className="w-full"
onClick={() => router.push('/dashboard/orders')}
>
View All Orders
</Button>
</div>
</>
)}
</TabsContent>
</Tabs>
</div>
</DropdownMenuContent>
</DropdownMenu>
);
}