mkcart / frontend /src /components /NotificationCenter.jsx
Kumar
updated
c2efbe6
import { useState, useEffect, useRef } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { toast } from "react-toastify"
import { useNavigate } from "react-router-dom"
import {
Bell,
X,
Check,
AlertCircle,
Info,
CheckCircle,
AlertTriangle,
Package,
ShoppingCart,
Heart,
Star,
Truck,
Gift,
Crown,
Zap,
Trash2,
Settings,
Eye,
EyeOff,
} from "lucide-react"
import {
subscribeToNotifications,
getNotifications,
markNotificationAsRead,
markAllNotificationsAsRead as markAllReadService,
deleteNotification as deleteNotificationService,
clearAllNotifications as clearAllService,
getUnreadCount as getUnreadCountService
} from "../utils/notificationService"
import "./NotificationCenter.css"
const NotificationCenter = () => {
const navigate = useNavigate()
const [isOpen, setIsOpen] = useState(false)
const [notifications, setNotifications] = useState([])
const [unreadCount, setUnreadCount] = useState(0)
const [filter, setFilter] = useState("all")
const [showSettings, setShowSettings] = useState(false)
const notificationRef = useRef(null)
const timeoutRef = useRef(null)
const notificationTypes = {
success: { icon: CheckCircle, color: "#4ecdc4", bgColor: "rgba(78, 205, 196, 0.1)" },
warning: { icon: AlertTriangle, color: "#f39c12", bgColor: "rgba(243, 156, 18, 0.1)" },
error: { icon: AlertCircle, color: "#e74c3c", bgColor: "rgba(231, 76, 60, 0.1)" },
info: { icon: Info, color: "#667eea", bgColor: "rgba(102, 126, 234, 0.1)" },
order: { icon: Package, color: "#9b59b6", bgColor: "rgba(155, 89, 182, 0.1)" },
cart: { icon: ShoppingCart, color: "#2ecc71", bgColor: "rgba(46, 204, 113, 0.1)" },
wishlist: { icon: Heart, color: "#e91e63", bgColor: "rgba(233, 30, 99, 0.1)" },
review: { icon: Star, color: "#ff9800", bgColor: "rgba(255, 152, 0, 0.1)" },
shipping: { icon: Truck, color: "#00bcd4", bgColor: "rgba(0, 188, 212, 0.1)" },
promotion: { icon: Gift, color: "#ff5722", bgColor: "rgba(255, 87, 34, 0.1)" },
}
useEffect(() => {
const unsubscribe = subscribeToNotifications((updatedNotifications) => {
setNotifications(updatedNotifications)
setUnreadCount(getUnreadCountService())
})
setNotifications(getNotifications())
setUnreadCount(getUnreadCountService())
return unsubscribe
}, [])
useEffect(() => {
const handleClickOutside = (event) => {
if (notificationRef.current && !notificationRef.current.contains(event.target)) {
setIsOpen(false)
setShowSettings(false)
}
}
document.addEventListener("mousedown", handleClickOutside)
return () => document.removeEventListener("mousedown", handleClickOutside)
}, [])
const generateRandomNotification = () => {
const types = Object.keys(notificationTypes)
const randomType = types[Math.floor(Math.random() * types.length)]
const messages = {
success: ["Payment processed successfully!", "Account verified!", "Settings updated!"],
warning: ["Low stock alert for wishlist item", "Payment method expires soon", "Profile incomplete"],
error: ["Payment failed", "Connection timeout", "Invalid coupon code"],
info: ["New features available", "System maintenance scheduled", "Privacy policy updated"],
order: ["New order received", "Order status updated", "Order ready for pickup"],
cart: ["Item added to cart", "Cart saved for later", "Price drop in cart"],
wishlist: ["Item back in stock", "Price drop alert", "Similar item suggestion"],
review: ["Review published", "Review helpful vote", "Review response received"],
shipping: ["Package out for delivery", "Delivery attempted", "Package delivered"],
promotion: ["Flash sale started", "Exclusive member offer", "Cashback available"],
}
const titles = {
success: ["Success!", "Completed", "Done"],
warning: ["Attention", "Notice", "Warning"],
error: ["Error", "Failed", "Problem"],
info: ["Information", "Update", "News"],
order: ["Order Update", "New Order", "Order Status"],
cart: ["Cart Update", "Shopping Cart", "Cart Alert"],
wishlist: ["Wishlist Alert", "Wishlist Update", "Saved Item"],
review: ["Review Update", "Customer Review", "Feedback"],
shipping: ["Shipping Update", "Delivery Status", "Package Info"],
promotion: ["Special Offer", "Promotion", "Deal Alert"],
}
return {
id: Date.now(),
type: randomType,
title: titles[randomType][Math.floor(Math.random() * titles[randomType].length)],
message: messages[randomType][Math.floor(Math.random() * messages[randomType].length)],
timestamp: new Date(),
read: false,
priority: ["low", "medium", "high"][Math.floor(Math.random() * 3)],
}
}
const formatTimestamp = (timestamp) => {
const now = new Date()
const diff = now - timestamp
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(diff / 3600000)
const days = Math.floor(diff / 86400000)
if (minutes < 1) return "Just now"
if (minutes < 60) return `${minutes}m ago`
if (hours < 24) return `${hours}h ago`
if (days < 7) return `${days}d ago`
return timestamp.toLocaleDateString()
}
const markAsRead = (id) => {
markNotificationAsRead(id)
setUnreadCount(getUnreadCountService())
}
const markAllAsRead = () => {
markAllReadService()
setUnreadCount(0)
}
const deleteNotification = (id) => {
deleteNotificationService(id)
setUnreadCount(getUnreadCountService())
toast.success("Notification removed!", {
position: "top-right",
autoClose: 2000,
})
}
const clearAllNotifications = () => {
clearAllService()
setUnreadCount(0)
toast.success("All notifications cleared!", {
position: "top-right",
autoClose: 2000,
})
}
const filteredNotifications = notifications.filter((notification) => {
if (filter === "all") return true
if (filter === "unread") return !notification.read
return notification.type === filter
})
const toggleNotificationPanel = () => {
setIsOpen(!isOpen)
}
const handleNotificationAction = (notification) => {
markAsRead(notification.id)
switch (notification.type) {
case 'order':
navigate('/orders')
toast.success('Navigating to Orders page')
break
case 'cart':
navigate('/cart')
toast.success('Navigating to Cart page')
break
case 'wishlist':
navigate('/wishlist')
toast.success('Navigating to Wishlist page')
break
case 'review':
navigate('/productlist')
toast.success('Navigating to Products page')
break
case 'shipping':
navigate('/orders')
toast.success('Navigating to Orders page')
break
case 'promotion':
navigate('/productlist')
toast.success('Navigating to Products page')
break
case 'success':
navigate('/profile')
toast.success('Navigating to Profile page')
break
case 'warning':
navigate('/profile')
toast.success('Navigating to Profile page')
break
case 'error':
navigate('/cart')
toast.success('Navigating to Cart page')
break
case 'info':
navigate('/productlist')
toast.success('Navigating to Products page')
break
default:
navigate('/productlist')
toast.success('Navigating to Products page')
break
}
setIsOpen(false)
}
return (
<div className="notification-center" ref={notificationRef}>
{}
<motion.button
className="notification-bell"
onClick={toggleNotificationPanel}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
animate={{
rotate: unreadCount > 0 ? [0, 15, -15, 0] : 0,
}}
transition={{
duration: 0.5,
repeat: unreadCount > 0 ? Number.POSITIVE_INFINITY : 0,
repeatDelay: 3,
}}
>
<Bell size={20} />
{}
{unreadCount > 0 && (
<motion.div
className="notification-badge"
initial={{ scale: 0, rotate: 180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: "spring", stiffness: 300 }}
>
{unreadCount > 99 ? "99+" : unreadCount}
</motion.div>
)}
{}
{unreadCount > 0 && (
<motion.div
className="notification-pulse"
initial={{ scale: 1, opacity: 0.8 }}
animate={{ scale: 1.5, opacity: 0 }}
transition={{
duration: 2,
repeat: Number.POSITIVE_INFINITY,
ease: "easeOut",
}}
/>
)}
</motion.button>
{}
<AnimatePresence>
{isOpen && (
<motion.div
className="notification-panel"
initial={{ opacity: 0, scale: 0.9, y: -20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: -20 }}
transition={{ duration: 0.3, type: "spring", stiffness: 200 }}
>
{}
<div className="panel-header">
<div className="header-content">
<div className="header-title">
<Crown size={20} />
<h3>Notifications</h3>
{unreadCount > 0 && <span className="unread-indicator">({unreadCount} new)</span>}
<span className="notification-limit">({notifications.length}/6)</span>
</div>
<div className="header-actions">
<motion.button
className="header-action-btn"
onClick={() => setShowSettings(!showSettings)}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<Settings size={16} />
</motion.button>
<motion.button
className="header-action-btn"
onClick={markAllAsRead}
disabled={unreadCount === 0}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<Check size={16} />
</motion.button>
<motion.button
className="header-action-btn close"
onClick={() => setIsOpen(false)}
whileHover={{ scale: 1.1, rotate: 90 }}
whileTap={{ scale: 0.9 }}
>
<X size={16} />
</motion.button>
</div>
</div>
{}
<AnimatePresence>
{showSettings && (
<motion.div
className="settings-panel"
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
>
<div className="settings-content">
<motion.button className="settings-action" onClick={clearAllNotifications} whileHover={{ x: 5 }}>
<Trash2 size={14} />
<span>Clear All</span>
</motion.button>
</div>
</motion.div>
)}
</AnimatePresence>
{}
<div className="filter-tabs">
{[
{ key: "all", label: "All", icon: Bell },
{ key: "unread", label: "Unread", icon: EyeOff },
{ key: "order", label: "Orders", icon: Package },
{ key: "promotion", label: "Offers", icon: Gift },
].map((tab) => (
<motion.button
key={tab.key}
className={`filter-tab ${filter === tab.key ? "active" : ""}`}
onClick={() => setFilter(tab.key)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<tab.icon size={14} />
<span>{tab.label}</span>
</motion.button>
))}
</div>
</div>
{}
<div className="notifications-list">
<AnimatePresence mode="popLayout">
{filteredNotifications.length === 0 ? (
<motion.div
className="empty-state"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
>
<Bell size={48} />
<h4>No notifications</h4>
<p>You're all caught up!</p>
</motion.div>
) : (
filteredNotifications.map((notification, index) => {
const NotificationIcon = notificationTypes[notification.type]?.icon || Info
const iconColor = notificationTypes[notification.type]?.color || "#667eea"
const bgColor = notificationTypes[notification.type]?.bgColor || "rgba(102, 126, 234, 0.1)"
return (
<motion.div
key={notification.id}
className={`notification-item ${notification.read ? "read" : "unread"} priority-${
notification.priority
}`}
initial={{ opacity: 0, x: -20, scale: 0.9 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, x: 20, scale: 0.9 }}
transition={{
duration: 0.3,
delay: index * 0.05,
type: "spring",
stiffness: 200,
}}
whileHover={{ x: 5, scale: 1.02 }}
layout
>
{}
<div className={`priority-indicator priority-${notification.priority}`} />
{}
<div className="notification-icon" style={{ backgroundColor: bgColor }}>
<NotificationIcon size={18} style={{ color: iconColor }} />
{!notification.read && <div className="unread-dot" />}
</div>
{}
<div className="notification-content">
<div className="notification-header">
<h4 className="notification-title">{notification.title}</h4>
<div className="notification-actions">
<span className="notification-time">{formatTimestamp(notification.timestamp)}</span>
<motion.button
className="action-btn"
onClick={() => deleteNotification(notification.id)}
whileHover={{ scale: 1.2, rotate: 90 }}
whileTap={{ scale: 0.8 }}
>
<X size={12} />
</motion.button>
</div>
</div>
<p className="notification-message">{notification.message}</p>
{}
<motion.button
className="notification-action-btn"
onClick={() => handleNotificationAction(notification)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Zap size={14} />
<span>View</span>
</motion.button>
</div>
{}
<motion.button
className="read-toggle"
onClick={() => markAsRead(notification.id)}
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.8 }}
>
{notification.read ? <Eye size={14} /> : <EyeOff size={14} />}
</motion.button>
{}
<div className="notification-glow" />
</motion.div>
)
})
)}
</AnimatePresence>
</div>
{}
{filteredNotifications.length > 0 && (
<div className="panel-footer">
<motion.button
className="view-all-btn"
onClick={() => {
navigate('/productlist')
setIsOpen(false)
}}
whileHover={{ scale: 1.02, y: -2 }}
whileTap={{ scale: 0.98 }}
>
<span>View All Notifications</span>
<div className="btn-glow" />
</motion.button>
</div>
)}
</motion.div>
)}
</AnimatePresence>
</div>
)
}
export default NotificationCenter