import React, { useEffect, useRef, useState } from 'react' export default function NotificationDropdown({ initialUnreadCount = 0, notificationsUrl = '/api/notifications' }) { const [open, setOpen] = useState(false) const [loading, setLoading] = useState(false) const [items, setItems] = useState([]) const [unreadCount, setUnreadCount] = useState(Number(initialUnreadCount || 0)) const rootRef = useRef(null) const csrfToken = typeof document !== 'undefined' ? document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') : null useEffect(() => { if (!open) return undefined const onDocumentClick = (event) => { if (rootRef.current && !rootRef.current.contains(event.target)) { setOpen(false) } } document.addEventListener('mousedown', onDocumentClick) return () => document.removeEventListener('mousedown', onDocumentClick) }, [open]) useEffect(() => { if (!open || items.length > 0) return setLoading(true) fetch(notificationsUrl, { headers: { Accept: 'application/json' }, credentials: 'same-origin', }) .then(async (response) => { if (!response.ok) throw new Error('Failed to load notifications') return response.json() }) .then((payload) => { setItems(Array.isArray(payload?.data) ? payload.data : []) setUnreadCount(Number(payload?.unread_count || 0)) }) .catch(() => {}) .finally(() => setLoading(false)) }, [items.length, notificationsUrl, open]) const markAllRead = async () => { try { await fetch('/api/notifications/read-all', { method: 'POST', headers: { Accept: 'application/json', 'X-CSRF-TOKEN': csrfToken || '', }, credentials: 'same-origin', }) setItems((current) => current.map((item) => ({ ...item, read: true }))) setUnreadCount(0) } catch { // Keep current state on failure. } } const markSingleRead = async (id) => { await fetch(`/api/notifications/${id}/read`, { method: 'POST', headers: { Accept: 'application/json', 'X-CSRF-TOKEN': csrfToken || '', }, credentials: 'same-origin', }) setItems((current) => current.map((item) => item.id === id ? { ...item, read: true } : item)) setUnreadCount((current) => Math.max(0, current - 1)) } const handleNotificationClick = async (event, item) => { if (!item?.id || item.read) return const href = event.currentTarget.getAttribute('href') if (!href) return event.preventDefault() try { await markSingleRead(item.id) } catch { // Continue to the destination even if marking as read fails. } window.location.assign(href) } return (
{open ? (

Notifications

Recent follows, likes, comments, and unlocks.

{unreadCount > 0 ? ( ) : null}
{loading ?
Loading notifications…
: null} {!loading && items.length === 0 ?
No notifications yet.
: null} {!loading && items.map((item) => ( handleNotificationClick(event, item)} className={[ 'flex gap-3 border-b border-white/[0.05] px-4 py-3 transition hover:bg-white/[0.04]', item.read ? 'bg-transparent' : 'bg-sky-500/[0.06]', ].join(' ')} > {item.actor?.name

{item.message}

{item.time_ago || ''}

))}