'use client'; import { useCallback, useEffect, useState } from 'react'; import { Bell, CircleAlert, Info, TriangleAlert, XIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Database } from '@kit/supabase/database'; import { Button } from '@kit/ui/button'; import { If } from '@kit/ui/if'; import { Popover, PopoverContent, PopoverTrigger } from '@kit/ui/popover'; import { Separator } from '@kit/ui/separator'; import { cn } from '@kit/ui/utils'; import { useDismissNotification, useFetchNotifications } from '../hooks'; type Notification = Database['public']['Tables']['notifications']['Row']; type PartialNotification = Pick< Notification, 'id' | 'body' | 'dismissed' | 'type' | 'created_at' | 'link' >; export function NotificationsPopover(params: { realtime: boolean; accountIds: string[]; onClick?: (notification: PartialNotification) => void; }) { const { i18n, t } = useTranslation(); const [open, setOpen] = useState(false); const [notifications, setNotifications] = useState([]); const onNotifications = useCallback( (notifications: PartialNotification[]) => { setNotifications((existing) => { const unique = new Set(existing.map((notification) => notification.id)); const notificationsFiltered = notifications.filter( (notification) => !unique.has(notification.id), ); return [...notificationsFiltered, ...existing]; }); }, [], ); const dismissNotification = useDismissNotification(); useFetchNotifications({ onNotifications, accountIds: params.accountIds, realtime: params.realtime, }); const timeAgo = (createdAt: string) => { const date = new Date(createdAt); let time: number; const daysAgo = Math.floor( (new Date().getTime() - date.getTime()) / (1000 * 60 * 60 * 24), ); const formatter = new Intl.RelativeTimeFormat(i18n.language, { numeric: 'auto', }); if (daysAgo < 1) { time = Math.floor((new Date().getTime() - date.getTime()) / (1000 * 60)); if (time < 5) { return t('common:justNow'); } if (time < 60) { return formatter.format(-time, 'minute'); } const hours = Math.floor(time / 60); return formatter.format(-hours, 'hour'); } const unit = (() => { const minutesAgo = Math.floor( (new Date().getTime() - date.getTime()) / (1000 * 60), ); if (minutesAgo <= 60) { return 'minute'; } if (daysAgo <= 1) { return 'hour'; } if (daysAgo <= 30) { return 'day'; } if (daysAgo <= 365) { return 'month'; } return 'year'; })(); const text = formatter.format(-daysAgo, unit); return text.slice(0, 1).toUpperCase() + text.slice(1); }; useEffect(() => { return () => { setNotifications([]); }; }, []); return (
{t('common:notifications')}
{t('common:noNotifications')}
{notifications.map((notification) => { const maxChars = 100; let body = t(notification.body, { defaultValue: notification.body, }); if (body.length > maxChars) { body = body.substring(0, maxChars) + '...'; } const Icon = () => { switch (notification.type) { case 'warning': return ; case 'error': return ; default: return ; } }; return (
{ if (params.onClick) { params.onClick(notification); } }} >
{(link) => ( {body} )}
{timeAgo(notification.created_at)}
); })}
); }