"use client"; import { useState, useRef, useEffect, useCallback } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Bell, Check, CheckCheck, Trash2, Film, AlertTriangle, CreditCard, Info, X } from "lucide-react"; import { useNotifications, useUnreadNotificationCount, useMarkNotificationAsRead, useMarkAllNotificationsAsRead, useDeleteNotification, } from "@/hooks/use-api"; import { cn } from "@/lib/utils"; import type { Notification } from "@/lib/api/api-service"; /** Bildirim tipine göre ikon ve renk */ function getNotificationMeta(type: string) { switch (type) { case "render_complete": return { icon: Film, color: "text-emerald-400", bg: "bg-emerald-500/12" }; case "render_failed": return { icon: AlertTriangle, color: "text-red-400", bg: "bg-red-500/12" }; case "credit_low": case "subscription_changed": return { icon: CreditCard, color: "text-amber-400", bg: "bg-amber-500/12" }; default: return { icon: Info, color: "text-violet-400", bg: "bg-violet-500/12" }; } } /** Tarih formatı — relative time */ function timeAgo(dateStr: string): string { const now = Date.now(); const date = new Date(dateStr).getTime(); const diff = Math.max(0, now - date); const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (seconds < 60) return "az önce"; if (minutes < 60) return `${minutes} dk önce`; if (hours < 24) return `${hours} sa önce`; if (days < 7) return `${days} gün önce`; return new Date(dateStr).toLocaleDateString("tr-TR", { day: "numeric", month: "short" }); } function NotificationItem({ notification, onMarkRead, onDelete, }: { notification: Notification; onMarkRead: (id: string) => void; onDelete: (id: string) => void; }) { const meta = getNotificationMeta(notification.type); const Icon = meta.icon; return ( {/* İkon */} {/* İçerik */} {notification.title} {notification.message && ( {notification.message} )} {timeAgo(notification.createdAt)} {/* Aksiyonlar — hover'da göster */} {!notification.isRead && ( { e.stopPropagation(); onMarkRead(notification.id); }} className="p-1.5 rounded-lg text-[var(--color-text-muted)] hover:text-emerald-400 hover:bg-emerald-500/10 transition-colors" title="Okundu işaretle" > )} { e.stopPropagation(); onDelete(notification.id); }} className="p-1.5 rounded-lg text-[var(--color-text-muted)] hover:text-red-400 hover:bg-red-500/10 transition-colors" title="Sil" > {/* Okunmamış göstergesi */} {!notification.isRead && ( )} ); } export function NotificationsDropdown() { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); const { data: unreadData } = useUnreadNotificationCount(); const { data: notifData, isLoading } = useNotifications({ limit: 20 }); const markRead = useMarkNotificationAsRead(); const markAllRead = useMarkAllNotificationsAsRead(); const deleteNotif = useDeleteNotification(); // Okunmamış sayısı — global interceptor wrap edebilir // eslint-disable-next-line @typescript-eslint/no-explicit-any const unreadCount = (unreadData as any)?.data?.count ?? (unreadData as any)?.count ?? 0; // eslint-disable-next-line @typescript-eslint/no-explicit-any const notifications: Notification[] = (notifData as any)?.data?.data ?? (notifData as any)?.data ?? []; // Dışarı tıklanınca kapat const handleClickOutside = useCallback((e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setIsOpen(false); } }, []); useEffect(() => { if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => document.removeEventListener("mousedown", handleClickOutside); }, [isOpen, handleClickOutside]); return ( {/* Tetikleyici Buton */} setIsOpen(!isOpen)} className={cn( "relative w-9 h-9 rounded-xl flex items-center justify-center transition-colors", isOpen ? "text-violet-400 bg-violet-500/10" : "text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-elevated)]" )} aria-label="Bildirimler" id="notifications-trigger" > {/* Badge */} {unreadCount > 0 && ( {unreadCount > 99 ? "99+" : unreadCount} )} {/* Dropdown Panel */} {isOpen && ( {/* Başlık */} Bildirimler {unreadCount > 0 && ( markAllRead.mutate()} className="flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium text-violet-400 hover:bg-violet-500/10 rounded-lg transition-colors" disabled={markAllRead.isPending} > Tümünü oku )} setIsOpen(false)} className="p-1 rounded-lg text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-elevated)] transition-colors md:hidden" > {/* Bildirim Listesi */} {isLoading ? ( Yükleniyor... ) : notifications.length === 0 ? ( Henüz bildirim yok Video render ve sistem olayları burada görünecek ) : ( {notifications.map((n) => ( markRead.mutate(id)} onDelete={(id) => deleteNotif.mutate(id)} /> ))} )} )} ); }
{notification.title}
{notification.message}
{timeAgo(notification.createdAt)}
Yükleniyor...
Henüz bildirim yok
Video render ve sistem olayları burada görünecek