T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/pages/notifications.tsx
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; 

CasperSecurity Mini