diff --git a/src/app/api/notifications/stream/route.ts b/src/app/api/notifications/stream/route.ts deleted file mode 100644 index 6d9718015..000000000 --- a/src/app/api/notifications/stream/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { auth } from '@clerk/nextjs/server' -import { connection, type NextRequest } from 'next/server' -import { logger } from '@/lib/logger' -import { - realtimeNotificationService, - createSSEResponse, -} from '@/server/notifications/realtimeService' - -export async function GET(request: NextRequest) { - await connection() - - try { - const { userId } = await auth() - - if (!userId) return new Response('Unauthorized', { status: 401 }) - - const stream = realtimeNotificationService.createSSEConnection(userId) - const origin = request.headers.get('origin') || undefined - - return createSSEResponse(stream, origin) - } catch (error) { - logger.error('SSE connection error:', error) - return new Response('Internal Server Error', { status: 500 }) - } -} diff --git a/src/components/notifications/NotificationCenter.tsx b/src/components/notifications/NotificationCenter.tsx index 5304169a3..ec0fe76a3 100644 --- a/src/components/notifications/NotificationCenter.tsx +++ b/src/components/notifications/NotificationCenter.tsx @@ -6,7 +6,7 @@ import { Bell, X } from 'lucide-react' import { useRouter } from 'next/navigation' import { useState, useEffect, type MouseEvent } from 'react' import { createPortal } from 'react-dom' -import { POLLING_INTERVALS } from '@/data/constants' +import { CACHE_DURATIONS, POLLING_INTERVALS } from '@/data/constants' import { api } from '@/lib/api' import toast from '@/lib/toast' import { cn } from '@/lib/utils' @@ -27,18 +27,20 @@ function NotificationCenter(props: Props) { const notificationsQuery = api.notifications.get.useQuery( { limit: 10, offset: 0 }, { - enabled: !!user, - refetchOnWindowFocus: true, - refetchInterval: POLLING_INTERVALS.NOTIFICATIONS, + enabled: !!user && isOpen, + refetchOnWindowFocus: isOpen, + refetchInterval: isOpen ? POLLING_INTERVALS.SHORT : false, + staleTime: CACHE_DURATIONS.VERY_SHORT, }, ) const unreadCountQuery = api.notifications.getUnreadCount.useQuery(undefined, { enabled: !!user, + staleTime: CACHE_DURATIONS.SHORT, refetchOnWindowFocus: true, - refetchInterval: POLLING_INTERVALS.NOTIFICATIONS, + refetchInterval: isOpen ? false : POLLING_INTERVALS.EXTRA_LONG, + refetchIntervalInBackground: false, }) - // Mutations const markAsReadMutation = api.notifications.markAsRead.useMutation({ onMutate: () => setIsLoading(true), onSuccess: () => { @@ -85,7 +87,15 @@ function NotificationCenter(props: Props) { router.push('/notifications') } - // Add escape key handler + const handleToggleNotifications = () => { + const nextIsOpen = !isOpen + setIsOpen(nextIsOpen) + + if (nextIsOpen) { + void utils.notifications.getUnreadCount.invalidate() + } + } + useEffect(() => { const handleEscape = (event: KeyboardEvent) => { if (event.key === 'Escape' && isOpen) { @@ -104,21 +114,17 @@ function NotificationCenter(props: Props) { } }, [isOpen]) - // Don't render anything if user is not authenticated if (!user) return null const handleNotificationClick = (notification: (typeof notifications)[0]) => { - // Mark as read if not already read (swallow error if it fails) if (!notification.isRead) { markAsReadMutation.mutateAsync({ notificationId: notification.id }).catch(console.error) } setIsOpen(false) - // Navigate based on actionUrl if available if (notification.actionUrl) return router.push(notification.actionUrl) - // Try to extract route from metadata if actionUrl is not available const metadata = notification.metadata as Record if (typeof metadata?.listingId === 'string') { router.push(`/listings/${metadata.listingId}`) @@ -127,7 +133,6 @@ function NotificationCenter(props: Props) { } else if (typeof metadata?.userId === 'string') { router.push(`/users/${metadata.userId}`) } else { - // Default to notifications page if no specific route router.push('/notifications') } } @@ -141,7 +146,6 @@ function NotificationCenter(props: Props) { } const handleBackdropClick = (ev: MouseEvent) => { - // Only close if the click is directly on the backdrop, not bubbling from child elements if (ev.target !== ev.currentTarget) return setIsOpen(false) } @@ -151,14 +155,13 @@ function NotificationCenter(props: Props) { return (
- {/* Notification Bell Button */} - {/* Desktop Dropdown */} {isOpen && ( - {/* Header */}

Notifications

@@ -212,7 +213,6 @@ function NotificationCenter(props: Props) {
- {/* Notifications List */}
- {/* Footer */} {notifications.length > 0 && (