'use client'; import { useCallback, useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Bell, Loader2, Trash2 } from 'lucide-react'; import { useAuth } from '@/components/id/auth-provider'; import { useToast } from '@/components/id/toast-provider'; import { Button } from '@/components/ui/button'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { AppNotification, fetchNotifications, fetchUnreadNotificationCount, getApiErrorMessage, markAllNotificationsRead, markNotificationRead, respondFamilyInvite } from '@/lib/api'; import { useRealtime } from '@/components/notifications/realtime-provider'; import { cn } from '@/lib/utils'; function formatTime(value: string) { return new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit', day: 'numeric', month: 'short' }).format( new Date(value) ); } export function NotificationBell() { const router = useRouter(); const { token, isPinLocked } = useAuth(); const { showToast } = useToast(); const { unreadCount, setUnreadCount, subscribe } = useRealtime(); const [open, setOpen] = useState(false); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [respondingId, setRespondingId] = useState(null); const load = useCallback(async () => { if (!token || isPinLocked) return; setLoading(true); try { const [list, count] = await Promise.all([fetchNotifications(token), fetchUnreadNotificationCount(token)]); setItems(list.notifications ?? []); setUnreadCount(count.count ?? 0); } catch (error) { const message = getApiErrorMessage(error, 'Не удалось загрузить уведомления'); if (message) showToast(message); } finally { setLoading(false); } }, [isPinLocked, setUnreadCount, showToast, token]); useEffect(() => { void load(); }, [load]); useEffect(() => { return subscribe((event) => { void load(); if (event.type === 'chat_message' && event.title) { showToast(`${event.title}: ${event.message}`); } if (event.type === 'family_invite') { showToast(event.message || 'Новое приглашение в семью'); } }); }, [load, showToast, subscribe]); async function openItem(item: AppNotification) { if (!token) return; await markNotificationRead(item.id, token); setUnreadCount((count) => Math.max(0, count - 1)); setItems((current) => current.filter((entry) => entry.id !== item.id)); let payload: Record = {}; try { payload = item.payloadJson ? JSON.parse(item.payloadJson) : {}; } catch { payload = {}; } if (payload.groupId) { setOpen(false); router.push(`/family/${payload.groupId}`); } } function removeInviteNotification(inviteId: string) { setItems((current) => { let removedUnread = 0; const next = current.filter((item) => { if (item.type !== 'family_invite') return true; try { const payload = item.payloadJson ? JSON.parse(item.payloadJson) as { inviteId?: string } : {}; if (payload.inviteId === inviteId) { if (!item.isRead) removedUnread += 1; return false; } } catch { // keep item if payload is malformed } return true; }); if (removedUnread > 0) { setUnreadCount((count) => Math.max(0, count - removedUnread)); } return next; }); } async function acceptInvite(inviteId: string) { if (!token) return; setRespondingId(inviteId); try { const invite = await respondFamilyInvite(inviteId, true, token); removeInviteNotification(inviteId); showToast('Вы присоединились к семье'); setOpen(false); router.push(`/family/${invite.groupId}`); } catch (error) { const message = getApiErrorMessage(error, 'Не удалось принять приглашение'); if (message) showToast(message); } finally { setRespondingId(null); } } async function declineInvite(inviteId: string) { if (!token) return; setRespondingId(inviteId); try { await respondFamilyInvite(inviteId, false, token); removeInviteNotification(inviteId); showToast('Приглашение отклонено'); } catch (error) { const message = getApiErrorMessage(error, 'Не удалось отклонить приглашение'); if (message) showToast(message); } finally { setRespondingId(null); } } async function markAllRead() { if (!token) return; await markAllNotificationsRead(token); setUnreadCount(0); setItems([]); } return (

Уведомления

{loading ? (
Загрузка...
) : items.length === 0 ? (

Уведомлений пока нет

) : ( items.map((item) => { let inviteId: string | undefined; try { inviteId = item.payloadJson ? (JSON.parse(item.payloadJson).inviteId as string) : undefined; } catch { inviteId = undefined; } return (
{item.type === 'family_invite' && inviteId ? (
) : null}
); }) )}
); }