diff --git a/app/app/notifications/page.tsx b/app/app/notifications/page.tsx index a81b033..39d7870 100644 --- a/app/app/notifications/page.tsx +++ b/app/app/notifications/page.tsx @@ -1,24 +1,71 @@ -import { useEffect, useState } from 'react'; +'use client'; + +import { useEffect, useState, useCallback } from 'react'; import { Badge } from '@/components/ui/badge'; +interface Notification { + id: string; + message: string; + link?: string; + isRead: boolean; + createdAt: string; +} + export default function NotificationsPage() { - const [notifications, setNotifications] = useState([]); + const [notifications, setNotifications] = useState([]); const [loading, setLoading] = useState(true); - useEffect(() => { - async function fetchNotifications() { - setLoading(true); + const fetchNotifications = useCallback(async () => { + setLoading(true); + try { const res = await fetch('/api/notifications?page=1&pageSize=50'); const data = await res.json(); setNotifications(data.notifications || []); + } finally { setLoading(false); } - fetchNotifications(); }, []); + useEffect(() => { + fetchNotifications(); + }, [fetchNotifications]); + + const markAsRead = async (id: string) => { + await fetch(`/api/notifications/${id}`, { method: 'PATCH' }); + setNotifications((prev) => + prev.map((n) => (n.id === id ? { ...n, isRead: true } : n)), + ); + }; + + const markAllAsRead = async () => { + const unread = notifications.filter((n) => !n.isRead); + await Promise.all( + unread.map((n) => fetch(`/api/notifications/${n.id}`, { method: 'PATCH' })), + ); + setNotifications((prev) => prev.map((n) => ({ ...n, isRead: true }))); + }; + + const unreadCount = notifications.filter((n) => !n.isRead).length; + return (
-

Notifications

+
+

+ Notifications + {unreadCount > 0 && ( + {unreadCount} + )} +

+ {unreadCount > 0 && ( + + )} +
+ {loading ? (
Loading...
) : notifications.length === 0 ? ( @@ -26,17 +73,34 @@ export default function NotificationsPage() { ) : ( diff --git a/app/contexts/app-context.tsx b/app/contexts/app-context.tsx index 58d95ed..5dcf531 100644 --- a/app/contexts/app-context.tsx +++ b/app/contexts/app-context.tsx @@ -262,6 +262,32 @@ export function AppProvider({ children }: { children: React.ReactNode }) { const contextValue: AppContextType = { ...state, + refreshPosts: async () => { + try { + const res = await fetch('/api/posts', { cache: 'no-store' }); + if (!res.ok) return; + const data = await res.json(); + dispatch({ + type: 'SET_POSTS', + payload: Array.isArray(data.data) ? data.data : data.data?.posts ?? [], + }); + } catch { + // silently ignore refresh failures + } + }, + refreshPostDetail: async (postId: string) => { + try { + const res = await fetch(`/api/posts/${postId}`, { cache: 'no-store' }); + if (!res.ok) return; + const data = await res.json(); + const post = data.data ?? data.post ?? null; + if (post) { + dispatch({ type: 'UPDATE_POST', payload: { id: postId, updates: post } }); + } + } catch { + // silently ignore refresh failures + } + }, login: async ( user: User, credentials: { diff --git a/app/lib/types.ts b/app/lib/types.ts index 8bd97ce..3c7a667 100644 --- a/app/lib/types.ts +++ b/app/lib/types.ts @@ -171,6 +171,8 @@ export interface AppContextType extends AppState { logout: () => Promise setCurrentUser: (user: User | null) => void // Post actions + refreshPosts: () => Promise + refreshPostDetail: (postId: string) => Promise createPost: (post: Omit) => void updatePost: (postId: string, updates: Partial) => void deletePost: (postId: string) => void