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/private_html/src/components/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/components/LawyerRequestsDashboard.tsx
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { motion, AnimatePresence } from 'framer-motion';
import { 
  User, CheckCircle, XCircle, Clock, MessageSquare, Star, 
  DollarSign, Calendar, Briefcase, Award, ThumbsUp, Eye
} from 'lucide-react';
import toast from 'react-hot-toast';
import LawyerRatingStars from './LawyerRatingStars';

interface LawyerRequest {
  id: string;
  caseId: string;
  lawyerId: string;
  message: string;
  proposedRate?: number;
  estimatedHours?: number;
  reasoning: string;
  status: 'PENDING' | 'APPROVED' | 'REJECTED';
  createdAt: string;
  lawyer: {
    id: string;
    name: string;
    profilePicture?: string;
    specialization?: string;
    averageRating?: number;
    proBono: boolean;
    isVerified: boolean;
    successRate?: number;
    totalCases: number;
    wonCases: number;
  };
}

interface Case {
  id: string;
  title: string;
  legalArea: string;
  urgencyLevel: string;
}

const LawyerRequestsDashboard: React.FC = () => {
  const { data: session } = useSession();
  const [requests, setRequests] = useState<LawyerRequest[]>([]);
  const [cases, setCases] = useState<Case[]>([]);
  const [loading, setLoading] = useState(true);
  const [selectedCase, setSelectedCase] = useState<string>('all');
  const [selectedRequest, setSelectedRequest] = useState<LawyerRequest | null>(null);
  const [showRequestModal, setShowRequestModal] = useState(false);

  useEffect(() => {
    if (session?.user?.id) {
      fetchMyCases();
      fetchLawyerRequests();
    }
  }, [session]);

  const fetchMyCases = async () => {
    try {
      const response = await fetch('/api/cases?myCases=true');
      if (response.ok) {
        const data = await response.json();
        setCases(data.cases || []);
      }
    } catch (error) {
      console.error('Error fetching cases:', error);
    }
  };

  const fetchLawyerRequests = async () => {
    try {
      setLoading(true);
      const params = selectedCase !== 'all' ? `?caseId=${selectedCase}` : '';
      const response = await fetch(`/api/lawyer-requests${params}`);
      
      if (response.ok) {
        const data = await response.json();
        setRequests(data.requests || []);
      } else {
        toast.error('Failed to load lawyer requests');
      }
    } catch (error) {
      console.error('Error fetching lawyer requests:', error);
      toast.error('Error loading lawyer requests');
    } finally {
      setLoading(false);
    }
  };

  const handleApproveRequest = async (requestId: string) => {
    try {
      const response = await fetch(`/api/lawyer-requests/${requestId}/approve`, {
        method: 'POST'
      });

      if (response.ok) {
        toast.success('Request approved! 🎉');
        fetchLawyerRequests();
      } else {
        toast.error('Failed to approve request');
      }
    } catch (error) {
      toast.error('Error approving request');
    }
  };

  const handleRejectRequest = async (requestId: string) => {
    try {
      const response = await fetch(`/api/lawyer-requests/${requestId}/reject`, {
        method: 'POST'
      });

      if (response.ok) {
        toast.success('Request rejected');
        fetchLawyerRequests();
      } else {
        toast.error('Failed to reject request');
      }
    } catch (error) {
      toast.error('Error rejecting request');
    }
  };

  const getStatusColor = (status: string) => {
    switch (status) {
      case 'PENDING': return 'bg-yellow-100 text-yellow-800 border-yellow-200';
      case 'APPROVED': return 'bg-green-100 text-green-800 border-green-200';
      case 'REJECTED': return 'bg-red-100 text-red-800 border-red-200';
      default: return 'bg-gray-100 text-gray-800 border-gray-200';
    }
  };

  const getStatusIcon = (status: string) => {
    switch (status) {
      case 'PENDING': return <Clock className="h-4 w-4" />;
      case 'APPROVED': return <CheckCircle className="h-4 w-4" />;
      case 'REJECTED': return <XCircle className="h-4 w-4" />;
      default: return <Clock className="h-4 w-4" />;
    }
  };

  if (loading) {
    return (
      <div className="flex justify-center items-center py-12">
        <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
      </div>
    );
  }

  return (
    <div className="space-y-6">
      {/* Header */}
      <div className="bg-white rounded-lg shadow-sm border p-6">
        <div className="flex items-center justify-between mb-4">
          <div>
            <h2 className="text-2xl font-bold text-gray-900">Lawyer Requests</h2>
            <p className="text-gray-600">Review and manage requests from lawyers to represent your cases</p>
          </div>
          <div className="text-right">
            <div className="text-2xl font-bold text-blue-600">{requests.length}</div>
            <div className="text-sm text-gray-600">Total Requests</div>
          </div>
        </div>

        {/* Case Filter */}
        <div className="flex items-center gap-4">
          <label className="text-sm font-medium text-gray-700">Filter by Case:</label>
          <select
            value={selectedCase}
            onChange={(e) => {
              setSelectedCase(e.target.value);
              fetchLawyerRequests();
            }}
            className="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
          >
            <option value="all">All Cases</option>
            {cases.map((case_) => (
              <option key={case_.id} value={case_.id}>
                {case_.title}
              </option>
            ))}
          </select>
        </div>
      </div>

      {/* Requests Grid */}
      <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
        {requests.map((request) => (
          <motion.div
            key={request.id}
            className="bg-white rounded-lg shadow-sm border hover:shadow-md transition-shadow"
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.3 }}
          >
            {/* Lawyer Info */}
            <div className="p-4 border-b border-gray-100">
              <div className="flex items-center gap-3 mb-3">
                <div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center">
                  {request.lawyer.profilePicture ? (
                    <img 
                      src={request.lawyer.profilePicture} 
                      alt={request.lawyer.name}
                      className="w-12 h-12 rounded-full object-cover"
                    />
                  ) : (
                    <User className="h-6 w-6 text-gray-500" />
                  )}
                </div>
                <div className="flex-1">
                  <div className="flex items-center gap-2">
                    <span className="font-semibold text-gray-900">{request.lawyer.name}</span>
                    {request.lawyer.isVerified && (
                      <CheckCircle className="h-4 w-4 text-blue-500" />
                    )}
                  </div>
                  <div className="text-sm text-gray-600">{request.lawyer.specialization}</div>
                </div>
              </div>

              {/* Lawyer Stats */}
              <div className="grid grid-cols-3 gap-2 text-xs">
                <div className="text-center">
                  <div className="font-semibold text-gray-900">{request.lawyer.successRate}%</div>
                  <div className="text-gray-600">Success</div>
                </div>
                <div className="text-center">
                  <div className="font-semibold text-gray-900">{request.lawyer.totalCases}</div>
                  <div className="text-gray-600">Cases</div>
                </div>
                <div className="text-center">
                  <div className="font-semibold text-gray-900">{request.lawyer.wonCases}</div>
                  <div className="text-gray-600">Won</div>
                </div>
              </div>
            </div>

            {/* Request Details */}
            <div className="p-4">
              <div className="mb-3">
                <span className={`inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-full border ${getStatusColor(request.status)}`}>
                  {getStatusIcon(request.status)}
                  {request.status}
                </span>
              </div>

              <div className="space-y-2 text-sm text-gray-600 mb-4">
                {request.proposedRate && (
                  <div className="flex items-center gap-2">
                    <DollarSign className="h-4 w-4" />
                    <span>${request.proposedRate}/hr</span>
                  </div>
                )}
                {request.estimatedHours && (
                  <div className="flex items-center gap-2">
                    <Clock className="h-4 w-4" />
                    <span>{request.estimatedHours} hours estimated</span>
                  </div>
                )}
                <div className="flex items-center gap-2">
                  <Calendar className="h-4 w-4" />
                  <span>{new Date(request.createdAt).toLocaleDateString()}</span>
                </div>
              </div>

              <div className="mb-4">
                <h4 className="font-medium text-gray-900 mb-2">Message:</h4>
                <p className="text-sm text-gray-600 line-clamp-3">{request.message}</p>
              </div>

              <div className="mb-4">
                <h4 className="font-medium text-gray-900 mb-2">Why They're the Best Fit:</h4>
                <p className="text-sm text-gray-600 line-clamp-3">{request.reasoning}</p>
              </div>

              {/* Actions */}
              {request.status === 'PENDING' && (
                <div className="flex gap-2">
                  <button
                    onClick={() => handleApproveRequest(request.id)}
                    className="flex-1 bg-green-600 text-white py-2 px-3 rounded-lg text-sm font-medium hover:bg-green-700 transition-colors flex items-center justify-center gap-1"
                  >
                    <CheckCircle className="h-4 w-4" />
                    Approve
                  </button>
                  <button
                    onClick={() => handleRejectRequest(request.id)}
                    className="flex-1 bg-red-600 text-white py-2 px-3 rounded-lg text-sm font-medium hover:bg-red-700 transition-colors flex items-center justify-center gap-1"
                  >
                    <XCircle className="h-4 w-4" />
                    Reject
                  </button>
                </div>
              )}

              {request.status !== 'PENDING' && (
                <div className="text-center">
                  <span className={`text-sm font-medium ${request.status === 'APPROVED' ? 'text-green-600' : 'text-red-600'}`}>
                    {request.status === 'APPROVED' ? '✓ Approved' : '✗ Rejected'}
                  </span>
                </div>
              )}
            </div>
          </motion.div>
        ))}
      </div>

      {/* Empty State */}
      {requests.length === 0 && (
        <div className="text-center py-12">
          <div className="text-6xl mb-4">📋</div>
          <h3 className="text-xl font-semibold text-gray-900 mb-2">No Lawyer Requests</h3>
          <p className="text-gray-600 mb-6">
            {selectedCase === 'all' 
              ? "You haven't received any lawyer requests yet. Make sure your cases are public to attract lawyers."
              : "No lawyers have requested to represent this case yet."
            }
          </p>
          <div className="flex justify-center gap-4">
            <button
              onClick={() => window.location.href = '/cases/new'}
              className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors"
            >
              Create New Case
            </button>
            <button
              onClick={() => window.location.href = '/live-cases'}
              className="bg-gray-100 text-gray-700 px-6 py-3 rounded-lg font-medium hover:bg-gray-200 transition-colors"
            >
              View Live Cases
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

export default LawyerRequestsDashboard;

CasperSecurity Mini