Silos/frontend/src/components/notifications/NotificationBell.tsx
AnuragJangid18 eed9504a53 feat: enhance workspace sidebar with create menu and document draft management
- Added a create menu in the WorkspaceSidebar for creating new documents (live docs, pages, whiteboards, databases, smart links, folders).
- Implemented document draft functionality to save and restore drafts for documents across various pages (ShedPage, SiloPage, SketchPage, SteadPage).
- Improved error handling in useDashboardDocuments for better user feedback on API errors.
- Introduced a NotificationBell component for displaying notifications with a read/unread state.
- Updated document update and publish logic to clear drafts upon successful updates.
- Enhanced user experience with keyboard shortcuts for editing and saving documents.
2026-10-23 21:54:48 +05:30

76 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useRef, useState } from 'react'
import { Bell, X } from 'lucide-react'
import { documentsApi, type ApiNotification } from '../../lib/api'
interface NotificationBellProps {
compact?: boolean
}
export default function NotificationBell({ compact = false }: NotificationBellProps) {
const [notifications, setNotifications] = useState<ApiNotification[]>([])
const [open, setOpen] = useState(false)
const rootRef = useRef<HTMLDivElement>(null)
useEffect(() => {
void documentsApi.listNotifications().then(setNotifications).catch(() => setNotifications([]))
}, [])
useEffect(() => {
function closeOnOutside(event: MouseEvent) {
if (rootRef.current && !rootRef.current.contains(event.target as Node)) setOpen(false)
}
document.addEventListener('mousedown', closeOnOutside)
return () => document.removeEventListener('mousedown', closeOnOutside)
}, [])
function toggle() {
const nextOpen = !open
setOpen(nextOpen)
if (nextOpen) {
void documentsApi.markNotificationsRead().then(() => {
setNotifications((items) => items.map((item) => ({ ...item, read: true })))
})
}
}
return (
<div ref={rootRef} className="relative">
<button
type="button"
onClick={toggle}
className={`relative rounded-lg text-silos-muted transition-colors hover:bg-silos-cream hover:text-silos-text ${
compact ? 'p-1.5' : 'p-2'
}`}
aria-label="Notifications"
aria-expanded={open}
>
<Bell className={compact ? 'h-3.5 w-3.5' : 'h-5 w-5'} />
{notifications.some((item) => !item.read) && (
<span className="absolute right-1 top-1 h-1.5 w-1.5 rounded-full bg-red-500" />
)}
</button>
{open && (
<div className="absolute right-0 top-10 z-50 w-80 rounded-xl border border-silos-cream-dark bg-white p-2 shadow-xl">
<div className="flex items-center justify-between px-2 py-1">
<p className="text-xs font-semibold text-silos-text">Notifications</p>
<button type="button" onClick={() => setOpen(false)} aria-label="Close notifications">
<X className="h-3.5 w-3.5 text-silos-muted" />
</button>
</div>
{notifications.length === 0 ? (
<p className="px-2 py-4 text-xs text-silos-muted">You’re all caught up.</p>
) : (
notifications.slice(0, 8).map((notification) => (
<div key={notification.id} className="border-t border-silos-cream px-2 py-2 text-xs text-silos-text">
<p>{notification.message}</p>
<time className="text-[10px] text-silos-muted">
{new Date(notification.created_at).toLocaleString()}
</time>
</div>
))
)}
</div>
)}
</div>
)
}