![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/public_html/src/pages/ |
import React, { useEffect, useState } from 'react';
import { Bell, MessageSquare, ThumbsUp, Smile, Flag } from 'lucide-react';
import { formatDistanceToNow } from 'date-fns';
import Link from 'next/link';
import LayoutWithSidebar from '../components/LayoutWithSidebar';
const PAGE_SIZE = 20;
const iconByType = (type: string) => {
switch (type) {
case 'reply': return <MessageSquare className="h-6 w-6 text-blue-500" />;
case 'like': return <ThumbsUp className="h-6 w-6 text-pink-500" />;
case 'reaction': return <Smile className="h-6 w-6 text-green-500" />;
case 'report': return <Flag className="h-6 w-6 text-red-500" />;
default: return <Bell className="h-6 w-6 text-gray-400" />;
}
};
const NotificationsPage: React.FC = () => {
const [notifications, setNotifications] = useState<any[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const [filter, setFilter] = useState<'all' | 'unread'>('all');
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);
const fetchNotifications = async (reset = false) => {
setLoading(true);
const res = await fetch(`/api/notifications?page=${reset ? 1 : page}&limit=${PAGE_SIZE}`);
if (res.ok) {
const data = await res.json();
if (reset) {
setNotifications(data);
} else {
setNotifications(prev => [...prev, ...data]);
}
setUnreadCount(data.filter((n: any) => !n.isRead).length);
setHasMore(data.length === PAGE_SIZE);
}
setLoading(false);
};
useEffect(() => {
fetchNotifications(true);
setPage(1);
}, [filter]);
useEffect(() => {
if (page > 1) fetchNotifications();
// eslint-disable-next-line
}, [page]);
const markAllAsRead = async () => {
const unreadIds = notifications.filter((n: any) => !n.isRead).map((n: any) => n.id);
if (unreadIds.length > 0) {
await fetch('/api/notifications', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids: unreadIds })
});
fetchNotifications(true);
}
};
const markAsRead = async (id: string) => {
await fetch('/api/notifications', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids: [id] })
});
fetchNotifications(true);
};
const filteredNotifications = filter === 'unread'
? notifications.filter(n => !n.isRead)
: notifications;
return (
<LayoutWithSidebar>
<div className="max-w-3xl mx-auto px-4 py-10">
<div className="flex items-center justify-between mb-6">
<h1 className="text-3xl font-bold text-gray-900 flex items-center gap-3">
<Bell className="h-8 w-8 text-blue-500" /> Notifications
</h1>
<button
className="text-sm px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
onClick={markAllAsRead}
disabled={unreadCount === 0}
>
Mark all as read
</button>
</div>
<div className="flex gap-4 mb-4">
<button
className={`px-3 py-1 rounded-lg text-sm font-medium ${filter === 'all' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700'}`}
onClick={() => setFilter('all')}
>
All
</button>
<button
className={`px-3 py-1 rounded-lg text-sm font-medium ${filter === 'unread' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700'}`}
onClick={() => setFilter('unread')}
>
Unread
</button>
</div>
<div className="bg-white rounded-xl shadow divide-y divide-gray-100">
{filteredNotifications.length === 0 ? (
<div className="p-8 text-center text-gray-400">No notifications.</div>
) : filteredNotifications.map(n => (
<div
key={n.id}
className={`flex items-start gap-4 px-6 py-5 transition-colors ${!n.isRead ? 'bg-yellow-50' : ''}`}
>
<div className="mt-1">{iconByType(n.type)}</div>
<div className="flex-1 min-w-0">
<div className={`font-semibold text-base ${!n.isRead ? 'text-gray-900' : 'text-gray-700'}`}>{n.title}</div>
<div className="text-sm text-gray-500 mb-1">{n.message}</div>
<div className="text-xs text-gray-400">{formatDistanceToNow(new Date(n.createdAt), { addSuffix: true })}</div>
</div>
{!n.isRead && (
<button
className="text-xs text-blue-600 hover:underline ml-2"
onClick={() => markAsRead(n.id)}
>
Mark as read
</button>
)}
</div>
))}
</div>
{hasMore && (
<div className="text-center mt-6">
<button
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
onClick={() => setPage(p => p + 1)}
disabled={loading}
>
{loading ? 'Loading...' : 'Load More'}
</button>
</div>
)}
</div>
</LayoutWithSidebar>
);
};
export default NotificationsPage;