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

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/components/CaseSelection.tsx
import React, { useState, useEffect } from 'react';
import { Calendar, MapPin, Users, AlertCircle, CheckCircle, Clock, Building, Scale, FileText } from 'lucide-react';

interface LegalCase {
  id: string;
  title: string;
  description: string;
  caseNumber?: string;
  caseType: string;
  jurisdiction: string;
  court?: string;
  firmName?: string;
  priority: string;
  applicationDeadline?: string;
  requiredDocuments: string[];
  eligibilityCriteria: any;
  logoUrl?: string;
  leadLawyer: {
    name: string;
    title?: string;
    specialization?: string;
    lawFirm?: {
      name: string;
      shortName?: string;
      address: string;
      city: string;
      province: string;
    };
  };
  applicationStats: {
    totalApplications: number;
    isAcceptingApplications: boolean;
  };
  caseUpdates: Array<{
    id: string;
    title: string;
    description: string;
    updateType: string;
    createdAt: string;
  }>;
}

interface CaseSelectionProps {
  selectedCaseId?: string;
  onCaseSelect: (caseId: string) => void;
  onCaseObjectSelect?: (caseObj: LegalCase) => void;
  isFrench?: boolean;
  className?: string;
}

const CaseSelection: React.FC<CaseSelectionProps> = ({
  selectedCaseId,
  onCaseSelect,
  onCaseObjectSelect,
  isFrench = false,
  className = ''
}) => {
  const [cases, setCases] = useState<LegalCase[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [expandedCase, setExpandedCase] = useState<string | null>(null);

  const texts = {
    title: isFrench ? 'Sélectionnez un Recours Collectif' : 'Select a Legal Case',
    subtitle: isFrench 
      ? 'Choisissez le recours collectif auquel vous souhaitez vous joindre'
      : 'Choose the legal case you want to join',
    loading: isFrench ? 'Chargement des recours...' : 'Loading cases...',
    error: isFrench ? 'Erreur lors du chargement des recours' : 'Error loading cases',
    noCase: isFrench ? 'Aucun recours disponible' : 'No cases available',
    noCaseDesc: isFrench 
      ? 'Il n\'y a actuellement aucun recours collectif acceptant de nouvelles demandes.'
      : 'There are currently no legal cases accepting new applications.',
    leadLawyer: isFrench ? 'Avocat principal' : 'Lead Lawyer',
    firm: isFrench ? 'Cabinet' : 'Law Firm',
    jurisdiction: isFrench ? 'Juridiction' : 'Jurisdiction',
    court: isFrench ? 'Tribunal' : 'Court',
    caseType: isFrench ? 'Type de recours' : 'Case Type',
    applications: isFrench ? 'demandes' : 'applications',
    deadline: isFrench ? 'Date limite' : 'Application Deadline',
    eligibility: isFrench ? 'Critères d\'éligibilité' : 'Eligibility Criteria',
    required: isFrench ? 'Documents requis' : 'Required Documents',
    updates: isFrench ? 'Mises à jour récentes' : 'Recent Updates',
    showMore: isFrench ? 'Voir plus de détails' : 'Show more details',
    showLess: isFrench ? 'Voir moins' : 'Show less',
    selectCase: isFrench ? 'Choisir ce recours' : 'Select this case',
    selected: isFrench ? 'Sélectionné' : 'Selected'
  };

  useEffect(() => {
    fetchCases();
  }, []);

  useEffect(() => {
    // If selectedCaseId is provided and not in the list, fetch it directly
    if (
      selectedCaseId &&
      !cases.some((c) => c.id === selectedCaseId)
    ) {
      fetch(`/api/public/cases/${selectedCaseId}`)
        .then((res) => res.ok ? res.json() : null)
        .then((caseData) => {
          const singleCase = caseData?.case || caseData;
          if (singleCase && singleCase.id) {
            setCases((prev) => {
              // Check if the case is already in the array to prevent duplicates
              const exists = prev.some(c => c.id === singleCase.id);
              return exists ? prev : [...prev, singleCase];
            });
          }
        });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedCaseId]);

  const fetchCases = async () => {
    try {
      const response = await fetch('/api/public/cases');
      if (response.ok) {
        const data = await response.json();
        setCases(Array.isArray(data.cases) ? data.cases : []);
      } else {
        setError('Failed to fetch cases');
        setCases([]);
      }
    } catch (err) {
      setError('Network error');
      setCases([]);
    } finally {
      setLoading(false);
    }
  };

  const formatDate = (dateString: string) => {
    return new Date(dateString).toLocaleDateString(isFrench ? 'fr-FR' : 'en-US', {
      year: 'numeric',
      month: 'long',
      day: 'numeric'
    });
  };

  const getPriorityColor = (priority: string) => {
    switch (priority) {
      case 'urgent': return 'text-red-600 bg-red-50 border-red-200';
      case 'high': return 'text-orange-600 bg-orange-50 border-orange-200';
      case 'medium': return 'text-blue-600 bg-blue-50 border-blue-200';
      case 'low': return 'text-gray-600 bg-gray-50 border-gray-200';
      default: return 'text-gray-600 bg-gray-50 border-gray-200';
    }
  };

  const formatCaseType = (type: string) => {
    return type.replace(/_/g, ' ')
               .split(' ')
               .map(word => word.charAt(0).toUpperCase() + word.slice(1))
               .join(' ');
  };

  // Deduplicate cases by id before rendering
  const uniqueCases = Array.from(new Map(cases.map(c => [c.id, c])).values());

  if (loading) {
    return (
      <div className={`bg-white rounded-lg shadow-sm border p-6 ${className}`}>
        <div className="animate-pulse space-y-4">
          <div className="h-6 bg-gray-200 rounded w-1/3"></div>
          <div className="space-y-3">
            <div className="h-20 bg-gray-200 rounded"></div>
            <div className="h-20 bg-gray-200 rounded"></div>
          </div>
        </div>
        <p className="text-center text-gray-500 mt-4">{texts.loading}</p>
      </div>
    );
  }

  if (error) {
    return (
      <div className={`bg-white rounded-lg shadow-sm border p-6 ${className}`}>
        <div className="text-center">
          <AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-4" />
          <h3 className="text-lg font-medium text-gray-900 mb-2">{texts.error}</h3>
          <p className="text-gray-600">{error}</p>
          <button
            onClick={fetchCases}
            className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
          >
            {isFrench ? 'Réessayer' : 'Retry'}
          </button>
        </div>
      </div>
    );
  }

  if (cases.length === 0) {
    return (
      <div className={`bg-white rounded-lg shadow-sm border p-6 ${className}`}>
        <div className="text-center">
          <Scale className="h-12 w-12 text-gray-400 mx-auto mb-4" />
          <h3 className="text-lg font-medium text-gray-900 mb-2">{texts.noCase}</h3>
          <p className="text-gray-600">{texts.noCaseDesc}</p>
        </div>
      </div>
    );
  }

  if (selectedCaseId) {
    const selected = uniqueCases.find(c => c.id === selectedCaseId);
    if (selected) {
      return (
        <div className={`bg-white rounded-lg shadow-sm border ${className}`}>
          <div className="p-6">
            <div className="mb-2">
              <span className="font-bold">You are applying to: {selected.title}</span>
              <span className="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">Locked</span>
            </div>
          </div>
        </div>
      );
    }
  }

  return (
    <div className={`bg-white rounded-lg shadow-sm border ${className}`}>
      <div className="p-6 border-b border-gray-200">
        <h2 className="text-xl font-semibold text-gray-900">{texts.title}</h2>
        <p className="text-gray-600 mt-1">{texts.subtitle}</p>
      </div>

      <div className="divide-y divide-gray-200">
        {uniqueCases.map((caseItem) => (
          <div
            key={caseItem.id}
            className={`p-6 transition-colors ${
              selectedCaseId === caseItem.id 
                ? 'bg-blue-50 border-l-4 border-l-blue-500' 
                : 'hover:bg-gray-50'
            }`}
          >
            <div className="space-y-4">
              {/* Case Header */}
              <div className="flex items-start gap-4">
                {/* Case Logo */}
                <div className="flex-shrink-0">
                  {caseItem.logoUrl ? (
                    <img
                      src={caseItem.logoUrl}
                      alt={`${caseItem.title} Logo`}
                      className="w-12 h-12 rounded-lg object-cover border border-gray-200"
                    />
                  ) : (
                    <div className="w-12 h-12 rounded-lg bg-gradient-to-br from-blue-500/20 to-indigo-500/20 border border-gray-200 flex items-center justify-center">
                      <FileText className="w-6 h-6 text-gray-600" />
                    </div>
                  )}
                </div>
                
                <div className="flex-1">
                  <div className="flex items-center gap-3 mb-2">
                    <h3 className="text-lg font-semibold text-gray-900">
                      {caseItem.title}
                    </h3>
                    {caseItem.caseNumber && (
                      <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded">
                        {caseItem.caseNumber}
                      </span>
                    )}
                    <span className={`text-xs px-2 py-1 rounded-full border ${getPriorityColor(caseItem.priority)}`}>
                      {caseItem.priority.toUpperCase()}
                    </span>
                  </div>
                  
                  <p className="text-gray-600 mb-3">
                    {caseItem.description.length > 200 && expandedCase !== caseItem.id
                      ? `${caseItem.description.substring(0, 200)}...`
                      : caseItem.description
                    }
                    {caseItem.description.length > 200 && (
                      <button
                        onClick={() => setExpandedCase(expandedCase === caseItem.id ? null : caseItem.id)}
                        className="text-blue-600 hover:text-blue-800 ml-2 text-sm"
                      >
                        {expandedCase === caseItem.id ? texts.showLess : texts.showMore}
                      </button>
                    )}
                  </p>
                </div>

                <div>
                  <button
                    className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors ${selectedCaseId === caseItem.id ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}
                    onClick={() => {
                      onCaseSelect(caseItem.id);
                      if (typeof onCaseObjectSelect === 'function') {
                        onCaseObjectSelect(caseItem);
                      }
                    }}
                    disabled={selectedCaseId === caseItem.id}
                  >
                    {selectedCaseId === caseItem.id ? texts.selected : texts.selectCase}
                  </button>
                </div>
              </div>

              {/* Case Details */}
              <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
                <div className="flex items-center gap-2 text-gray-600">
                  <Scale className="h-4 w-4" />
                  <div>
                    <span className="font-medium">{texts.leadLawyer}:</span>
                    <div>{caseItem.leadLawyer.name}</div>
                    {caseItem.leadLawyer.title && (
                      <div className="text-xs text-gray-500">{caseItem.leadLawyer.title}</div>
                    )}
                  </div>
                </div>

                <div className="flex items-center gap-2 text-gray-600">
                  <Building className="h-4 w-4" />
                  <div>
                    <span className="font-medium">{texts.firm}:</span>
                    <div>{caseItem.leadLawyer.lawFirm?.shortName || caseItem.firmName}</div>
                    {caseItem.leadLawyer.lawFirm?.city && (
                      <div className="text-xs text-gray-500">
                        {caseItem.leadLawyer.lawFirm.city}, {caseItem.leadLawyer.lawFirm.province}
                      </div>
                    )}
                  </div>
                </div>

                <div className="flex items-center gap-2 text-gray-600">
                  <MapPin className="h-4 w-4" />
                  <div>
                    <span className="font-medium">{texts.jurisdiction}:</span>
                    <div>{caseItem.jurisdiction}</div>
                    {caseItem.court && (
                      <div className="text-xs text-gray-500">{caseItem.court}</div>
                    )}
                  </div>
                </div>

                <div className="flex items-center gap-2 text-gray-600">
                  <Clock className="h-4 w-4" />
                  <div>
                    <span className="font-medium">{texts.caseType}:</span>
                    <div>{formatCaseType(caseItem.caseType)}</div>
                  </div>
                </div>

                <div className="flex items-center gap-2 text-gray-600">
                  <Users className="h-4 w-4" />
                  <div>
                    <span className="font-medium">{caseItem.applicationStats?.totalApplications || 0}</span> {texts.applications}
                  </div>
                </div>

                {caseItem.applicationDeadline && (
                  <div className="flex items-center gap-2 text-gray-600">
                    <Calendar className="h-4 w-4" />
                    <div>
                      <span className="font-medium">{texts.deadline}:</span>
                      <div>{formatDate(caseItem.applicationDeadline)}</div>
                    </div>
                  </div>
                )}
              </div>

              {/* Expanded Details */}
              {expandedCase === caseItem.id && (
                <div className="mt-4 pt-4 border-t border-gray-200 space-y-4">
                  {caseItem.eligibilityCriteria && Object.keys(caseItem.eligibilityCriteria).length > 0 && (
                    <div>
                      <h4 className="font-medium text-gray-900 mb-2">{texts.eligibility}</h4>
                      <div className="text-sm text-gray-600 bg-gray-50 p-3 rounded">
                        {JSON.stringify(caseItem.eligibilityCriteria, null, 2)}
                      </div>
                    </div>
                  )}

                  {caseItem.requiredDocuments && caseItem.requiredDocuments.length > 0 && (
                    <div>
                      <h4 className="font-medium text-gray-900 mb-2">{texts.required}</h4>
                      <ul className="text-sm text-gray-600 list-disc list-inside">
                        {caseItem.requiredDocuments.map((doc, index) => (
                          <li key={index}>{doc.replace(/_/g, ' ').toLowerCase()}</li>
                        ))}
                      </ul>
                    </div>
                  )}

                  {caseItem.caseUpdates && caseItem.caseUpdates.length > 0 && (
                    <div>
                      <h4 className="font-medium text-gray-900 mb-2">{texts.updates}</h4>
                      <div className="space-y-2">
                        {caseItem.caseUpdates.slice(0, 2).map((update) => (
                          <div key={update.id} className="text-sm p-3 bg-blue-50 rounded">
                            <div className="font-medium text-blue-900">{update.title}</div>
                            <div className="text-blue-700 mt-1">{update.description}</div>
                            <div className="text-blue-600 text-xs mt-1">
                              {formatDate(update.createdAt)}
                            </div>
                          </div>
                        ))}
                      </div>
                    </div>
                  )}
                </div>
              )}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default CaseSelection; 

CasperSecurity Mini