89 lines
2.5 KiB
JavaScript
89 lines
2.5 KiB
JavaScript
import React, { useEffect, useMemo, useState } from 'react'
|
|
import { getEcho } from '../../bootstrap'
|
|
|
|
export default function MessageInboxBadge({ initialUnreadCount = 0, userId = null, href = '/messages' }) {
|
|
const [unreadCount, setUnreadCount] = useState(Math.max(0, Number(initialUnreadCount || 0)))
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
|
|
const loadUnreadState = async () => {
|
|
try {
|
|
const response = await fetch('/api/messages/conversations', {
|
|
headers: { Accept: 'application/json' },
|
|
credentials: 'same-origin',
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load unread conversations')
|
|
}
|
|
|
|
const payload = await response.json()
|
|
if (cancelled) {
|
|
return
|
|
}
|
|
|
|
if (cancelled) {
|
|
return
|
|
}
|
|
|
|
const nextUnreadTotal = Number(payload?.summary?.unread_total)
|
|
if (Number.isFinite(nextUnreadTotal)) {
|
|
setUnreadCount(Math.max(0, nextUnreadTotal))
|
|
}
|
|
} catch {
|
|
// Keep server-rendered count if bootstrap fetch fails.
|
|
}
|
|
}
|
|
|
|
loadUnreadState()
|
|
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!userId) {
|
|
return undefined
|
|
}
|
|
|
|
const echo = getEcho()
|
|
if (!echo) {
|
|
return undefined
|
|
}
|
|
|
|
const channel = echo.private(`user.${userId}`)
|
|
const handleConversationUpdated = (payload) => {
|
|
const nextUnreadTotal = Number(payload?.summary?.unread_total)
|
|
if (Number.isFinite(nextUnreadTotal)) {
|
|
setUnreadCount(Math.max(0, nextUnreadTotal))
|
|
}
|
|
}
|
|
|
|
channel.listen('.conversation.updated', handleConversationUpdated)
|
|
|
|
return () => {
|
|
channel.stopListening('.conversation.updated', handleConversationUpdated)
|
|
echo.leaveChannel(`private-user.${userId}`)
|
|
}
|
|
}, [userId])
|
|
|
|
return (
|
|
<a
|
|
href={href}
|
|
className="relative inline-flex h-9 w-9 lg:h-10 lg:w-10 items-center justify-center rounded-lg hover:bg-white/5"
|
|
title="Messages"
|
|
>
|
|
<svg className="h-[18px] w-[18px] lg:h-5 lg:w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
</svg>
|
|
{unreadCount > 0 ? (
|
|
<span className="absolute -bottom-1 right-0 rounded border border-sb-line bg-red-700/70 px-1 py-0 text-[10px] tabular-nums text-white lg:px-1.5 lg:py-0.5 lg:text-[11px]">
|
|
{unreadCount > 99 ? '99+' : unreadCount}
|
|
</span>
|
|
) : null}
|
|
</a>
|
|
)
|
|
}
|