![]() 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/ |
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import Link from 'next/link';
import { motion, AnimatePresence } from 'framer-motion';
import {
Eye, MessageCircle, Users, Clock, DollarSign, Badge, Star,
Heart, Share2, BookOpen, Scale, AlertTriangle, TrendingUp,
Filter, Search, Calendar, MapPin, User, Award, Briefcase,
ExternalLink, FileText
} from 'lucide-react';
import toast from 'react-hot-toast';
import LawyerRatingStars from './LawyerRatingStars';
import { CompetitionCountdown } from './CompetitionCountdown';
interface PublicCase {
id: string;
title: string;
description: string;
publicSummary?: string;
category: string;
legalArea: string;
urgencyLevel: 'URGENT' | 'HIGH' | 'NORMAL' | 'LOW';
status: string;
jurisdiction: string;
court?: string;
viewCount: number;
supporterCount: number;
estimatedValue?: number;
riskLevel: 'HIGH' | 'MEDIUM' | 'LOW';
tags?: string[];
createdAt: string;
updatedAt: string;
competitionDeadline?: string;
minimumBid?: number;
currentHighestBid?: number;
totalBidders?: number;
averageBidAmount?: number;
competitionType?: 'AUCTION' | 'TENDER' | 'NEGOTIATION';
logoUrl?: string;
leadLawyer: {
id: string;
name: string;
username?: string;
profilePicture?: string;
specialization?: string;
averageRating?: number;
totalCases: number;
wonCases: number;
proBono: boolean;
hourlyRate?: number;
isVerified: boolean;
successRate?: number;
totalEarnings?: number;
responseTime?: number;
clientSatisfaction?: number;
};
creator: {
id: string;
name: string;
username?: string;
};
_count: {
offers: number;
registrations: number;
supporters: number;
views: number;
};
}
interface LawyerOffer {
id: string;
lawyerId: string;
message?: string;
proposedRate?: number;
estimatedHours?: number;
createdAt: string;
lawyer: {
id: string;
name: string;
profilePicture?: string;
specialization?: string;
averageRating?: number;
proBono: boolean;
isVerified: boolean;
};
}
const PublicCaseFeed: React.FC = () => {
const { data: session } = useSession();
const [cases, setCases] = useState<PublicCase[]>([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('all');
const [sortBy, setSortBy] = useState('newest');
const [searchTerm, setSearchTerm] = useState('');
const [selectedCase, setSelectedCase] = useState<PublicCase | null>(null);
const [offers, setOffers] = useState<LawyerOffer[]>([]);
const [showOfferModal, setShowOfferModal] = useState(false);
const [showCompetitionModal, setShowCompetitionModal] = useState(false);
const [competitionData, setCompetitionData] = useState<any>(null);
const [realTimeUpdates, setRealTimeUpdates] = useState(false);
const [showBidHistoryModal, setShowBidHistoryModal] = useState(false);
const [bidHistory, setBidHistory] = useState<any[]>([]);
const [showParticipantsModal, setShowParticipantsModal] = useState(false);
const [participants, setParticipants] = useState<any[]>([]);
useEffect(() => {
fetchPublicCases();
}, [filter, sortBy]);
const fetchPublicCases = async () => {
try {
setLoading(true);
const params = new URLSearchParams({
filter,
sortBy,
search: searchTerm
});
const response = await fetch(`/api/public/cases?${params}`);
if (response.ok) {
const data = await response.json();
// Ensure cases is an array with defensive programming
setCases(Array.isArray(data.cases) ? data.cases : []);
} else {
console.error('Failed to load cases:', response.status, response.statusText);
toast.error('Failed to load cases');
setCases([]); // Set empty array on error
}
} catch (error) {
console.error('Error fetching cases:', error);
toast.error('Error loading cases');
setCases([]); // Set empty array on error
} finally {
setLoading(false);
}
};
const handleViewCase = async (caseId: string) => {
// Update view count
await fetch(`/api/public/cases/${caseId}/view`, { method: 'POST' });
// Find and update local state
setCases(prev => prev.map(c =>
c.id === caseId ? { ...c, viewCount: c.viewCount + 1 } : c
));
};
const handleSupportCase = async (caseId: string) => {
if (!session) {
toast.error('Please login to support cases');
return;
}
try {
const response = await fetch(`/api/public/cases/${caseId}/support`, {
method: 'POST'
});
if (response.ok) {
setCases(prev => prev.map(c =>
c.id === caseId ? { ...c, supporterCount: c.supporterCount + 1 } : c
));
toast.success('Case supported! ๐ช');
}
} catch (error) {
toast.error('Failed to support case');
}
};
const handleOfferToRepresent = async (caseId: string) => {
if (!session || session.user.role !== 'LAWYER') {
toast.error('Only verified lawyers can offer representation');
return;
}
const selectedCaseData = cases.find(c => c.id === caseId);
setSelectedCase(selectedCaseData || null);
setShowOfferModal(true);
};
const submitOffer = async (offerData: {
message: string;
proposedRate?: number;
estimatedHours?: number;
}) => {
if (!selectedCase) return;
try {
const response = await fetch(`/api/cases/${selectedCase.id}/offers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(offerData)
});
if (response.ok) {
toast.success('Offer submitted successfully! ๐ฏ');
setShowOfferModal(false);
// Update offer count
setCases(prev => prev.map(c =>
c.id === selectedCase.id
? { ...c, _count: { ...c._count, offers: c._count.offers + 1 } }
: c
));
} else {
toast.error('Failed to submit offer');
}
} catch (error) {
toast.error('Error submitting offer');
}
};
const handleJoinCompetition = async (caseId: string) => {
if (!session || session.user.role !== 'LAWYER') {
toast.error('Only verified lawyers can join competitions');
return;
}
try {
const response = await fetch(`/api/public/cases/${caseId}/competition/join`, {
method: 'POST'
});
if (response.ok) {
toast.success('Successfully joined competition! ๐');
fetchPublicCases(); // Refresh data
} else {
toast.error('Failed to join competition');
}
} catch (error) {
toast.error('Error joining competition');
}
};
const handlePlaceBid = async (caseId: string, bidAmount: number, message: string) => {
try {
const response = await fetch(`/api/public/cases/${caseId}/competition/bid`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bidAmount, message })
});
if (response.ok) {
toast.success('Bid placed successfully! ๐ฐ');
setShowCompetitionModal(false);
fetchPublicCases(); // Refresh data
} else {
toast.error('Failed to place bid');
}
} catch (error) {
toast.error('Error placing bid');
}
};
const openBidHistoryModal = async (caseId: string) => {
setShowBidHistoryModal(true);
try {
const res = await fetch(`/api/public/cases/${caseId}/competition/bids`);
if (res.ok) {
const data = await res.json();
setBidHistory(data.bids);
} else {
setBidHistory([]);
}
} catch {
setBidHistory([]);
}
};
const openParticipantsModal = async (caseId: string) => {
setShowParticipantsModal(true);
try {
const res = await fetch(`/api/public/cases/${caseId}/competition/participants`);
if (res.ok) {
const data = await res.json();
setParticipants(data.participants);
} else {
setParticipants([]);
}
} catch {
setParticipants([]);
}
};
const getUrgencyColor = (urgency: string) => {
switch (urgency) {
case 'URGENT': return 'bg-red-100 text-red-800 border-red-200';
case 'HIGH': return 'bg-orange-100 text-orange-800 border-orange-200';
case 'NORMAL': return 'bg-blue-100 text-blue-800 border-blue-200';
case 'LOW': return 'bg-green-100 text-green-800 border-green-200';
default: return 'bg-gray-100 text-gray-800 border-gray-200';
}
};
const getRiskColor = (risk: string) => {
switch (risk) {
case 'HIGH': return 'text-red-600';
case 'MEDIUM': return 'text-yellow-600';
case 'LOW': return 'text-green-600';
default: return 'text-gray-600';
}
};
const getCompetitionStatus = (case_: PublicCase) => {
if (!case_.competitionDeadline) return null;
const deadline = new Date(case_.competitionDeadline);
const now = new Date();
const timeLeft = deadline.getTime() - now.getTime();
if (timeLeft <= 0) return 'ENDED';
if (timeLeft <= 24 * 60 * 60 * 1000) return 'ENDING_SOON';
return 'ACTIVE';
};
const getCompetitionBadge = (case_: PublicCase) => {
const status = getCompetitionStatus(case_);
if (!status) return null;
const badges = {
ACTIVE: { text: '๐ฅ Live Auction', color: 'bg-green-100 text-green-800 border-green-200' },
ENDING_SOON: { text: 'โฐ Ending Soon', color: 'bg-orange-100 text-orange-800 border-orange-200' },
ENDED: { text: '๐ Auction Ended', color: 'bg-gray-100 text-gray-800 border-gray-200' }
};
return badges[status];
};
if (loading) {
return (
<div className="space-y-6">
{[1, 2, 3].map(i => (
<div key={i} className="bg-white rounded-lg shadow-sm p-6 animate-pulse">
<div className="h-6 bg-gray-200 rounded w-3/4 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
</div>
))}
</div>
);
}
return (
<div className="max-w-6xl mx-auto">
{/* Enhanced Header & Stats */}
<div className="bg-gradient-to-r from-blue-50 to-purple-50 rounded-2xl p-8 mb-8 border border-blue-100">
<div className="text-center mb-8">
<div className="inline-flex items-center gap-3 bg-white/80 backdrop-blur-sm rounded-full px-6 py-3 mb-6 shadow-lg">
<span className="text-2xl">โ๏ธ</span>
<span className="font-bold text-gray-800">Live Legal Marketplace</span>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
๐๏ธ Public Case Arena
</h1>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-8">
Where justice meets transparency. Real cases seeking real representation.
</p>
{/* Live Competition Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 max-w-4xl mx-auto">
<div className="bg-white rounded-xl p-4 shadow-md border border-blue-100">
<div className="text-2xl font-bold text-blue-600 mb-1">๐ฅ</div>
<div className="text-sm font-semibold text-gray-800">Live Auctions</div>
<div className="text-xs text-gray-600">{cases.filter(c => getCompetitionStatus(c) === 'ACTIVE').length} Active</div>
</div>
<div className="bg-white rounded-xl p-4 shadow-md border border-green-100">
<div className="text-2xl font-bold text-green-600 mb-1">โ๏ธ</div>
<div className="text-sm font-semibold text-gray-800">Total Bidders</div>
<div className="text-xs text-gray-600">{cases.reduce((sum, c) => sum + (c.totalBidders || 0), 0)} Lawyers</div>
</div>
<div className="bg-white rounded-xl p-4 shadow-md border border-purple-100">
<div className="text-2xl font-bold text-purple-600 mb-1">๐ฐ</div>
<div className="text-sm font-semibold text-gray-800">Total Value</div>
<div className="text-xs text-gray-600">${cases.reduce((sum, c) => sum + (c.currentHighestBid || 0), 0).toLocaleString()}</div>
</div>
<div className="bg-white rounded-xl p-4 shadow-md border border-orange-100">
<div className="text-2xl font-bold text-orange-600 mb-1">๐ฏ</div>
<div className="text-sm font-semibold text-gray-800">Success Rate</div>
<div className="text-xs text-gray-600">95%+</div>
</div>
</div>
</div>
{/* Enhanced Filters */}
<div className="bg-white rounded-xl shadow-lg p-6">
<div className="flex flex-wrap gap-4 items-center">
<div className="flex-1 min-w-64">
<div className="relative">
<Search className="h-4 w-4 absolute left-3 top-3 text-gray-400" />
<input
type="text"
placeholder="Search cases by title, area of law, location..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onKeyPress={(e) => e.key === 'Enter' && fetchPublicCases()}
/>
</div>
</div>
<select
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white"
>
<option value="all">All Cases</option>
<option value="urgent">๐จ Urgent Cases</option>
<option value="criminal">โ๏ธ Criminal Law</option>
<option value="civil">๐ Civil Law</option>
<option value="human_rights">๐๏ธ Human Rights</option>
<option value="immigration">๐ Immigration</option>
<option value="family">๐จโ๐ฉโ๐งโ๐ฆ Family Law</option>
<option value="corporate">๐ข Corporate Law</option>
<option value="auction">๐ฅ Live Auctions</option>
<option value="ending_soon">โฐ Ending Soon</option>
</select>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white"
>
<option value="newest">๐ Newest First</option>
<option value="urgent">๐จ Most Urgent</option>
<option value="popular">๐๏ธ Most Popular</option>
<option value="supported">โค๏ธ Most Supported</option>
<option value="offers">๐ฌ Most Offers</option>
<option value="highest_bid">๐ฐ Highest Bid</option>
<option value="deadline">โฐ Deadline Soon</option>
</select>
</div>
</div>
</div>
{/* Cases Grid */}
<div className="space-y-6">
<AnimatePresence>
{cases.map((case_) => (
<motion.div
key={case_.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className={`bg-white rounded-xl shadow-lg border-2 overflow-hidden hover:shadow-xl transition-all duration-300 ${
case_.competitionType && getCompetitionStatus(case_) === 'ACTIVE'
? 'border-blue-200 bg-gradient-to-r from-blue-50 to-white'
: 'border-gray-200'
}`}
>
{/* Case Header */}
<div className="p-6">
<div className="flex items-start gap-4 mb-4">
{/* Case Logo */}
<div className="flex-shrink-0">
{case_.logoUrl ? (
<img
src={case_.logoUrl}
alt={`${case_.title} Logo`}
className="w-16 h-16 rounded-xl object-cover border-2 border-gray-200 shadow-sm"
/>
) : (
<div className="w-16 h-16 rounded-xl bg-gradient-to-br from-blue-500/20 to-indigo-500/20 border-2 border-gray-200 flex items-center justify-center">
<FileText className="w-8 h-8 text-gray-600" />
</div>
)}
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<h3 className="text-xl font-bold text-gray-900">{case_.title}</h3>
<span className={`px-2 py-1 rounded-full text-xs font-medium border ${getUrgencyColor(case_.urgencyLevel)}`}>
{case_.urgencyLevel}
</span>
{/* Competition Badge */}
{getCompetitionBadge(case_) && (
<span className={`px-2 py-1 rounded-full text-xs font-medium border ${getCompetitionBadge(case_)?.color}`}>
{getCompetitionBadge(case_)?.text}
</span>
)}
</div>
<p className="text-gray-600 mb-3 line-clamp-2">
{case_.publicSummary || case_.description}
</p>
{/* Enhanced Case Stats */}
<div className="flex flex-wrap gap-4 text-sm text-gray-500 mb-4">
<div className="flex items-center gap-1">
<MapPin className="h-4 w-4" />
{case_.jurisdiction}
</div>
<div className="flex items-center gap-1">
<Eye className="h-4 w-4" />
{case_.viewCount} views
</div>
<div className="flex items-center gap-1">
<Heart className="h-4 w-4" />
{case_.supporterCount} supporters
</div>
<div className="flex items-center gap-1">
<Users className="h-4 w-4" />
{case_._count.offers} offers
</div>
{/* Competition Stats */}
{case_.competitionType && (
<>
<div className="flex items-center gap-1">
<DollarSign className="h-4 w-4" />
${case_.currentHighestBid?.toLocaleString() || 0} highest bid
</div>
<div className="flex items-center gap-1">
<Users className="h-4 w-4" />
{case_.totalBidders || 0} bidders
</div>
{case_.competitionDeadline && (
<CompetitionCountdown
deadline={case_.competitionDeadline}
onEnd={() => {
fetchPublicCases();
toast.success('Competition has ended! Check the results.');
}}
/>
)}
</>
)}
</div>
{/* Lead Lawyer Info */}
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
<div className="flex items-center gap-2">
{case_.leadLawyer.profilePicture ? (
<img
src={case_.leadLawyer.profilePicture}
alt={case_.leadLawyer.name}
className="w-8 h-8 rounded-full"
/>
) : (
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-medium">
{case_.leadLawyer.name.charAt(0)}
</div>
)}
<div>
<div className="font-medium text-gray-900">{case_.leadLawyer.name}</div>
<div className="text-sm text-gray-600">
{case_.leadLawyer.specialization} โข {case_.leadLawyer.wonCases}/{case_.leadLawyer.totalCases} cases won
{case_.leadLawyer.successRate && ` โข ${case_.leadLawyer.successRate}% success rate`}
</div>
</div>
</div>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="flex flex-wrap gap-3">
<Link
href={`/public/cases/${case_.id}`}
onClick={() => handleViewCase(case_.id)}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Eye className="h-4 w-4" />
View Details
</Link>
<button
onClick={() => handleSupportCase(case_.id)}
className="flex items-center gap-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<Heart className="h-4 w-4" />
Support Case
</button>
{/* Enhanced Competition Actions */}
{case_.competitionType && getCompetitionStatus(case_) === 'ACTIVE' && (
<>
<button
onClick={() => handleJoinCompetition(case_.id)}
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
>
<Award className="h-4 w-4" />
Join Auction
</button>
<button
onClick={() => {
setSelectedCase(case_);
setShowCompetitionModal(true);
}}
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
>
<DollarSign className="h-4 w-4" />
Place Bid
</button>
<button
onClick={() => openBidHistoryModal(case_.id)}
className="flex items-center gap-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<Scale className="h-4 w-4" />
Bid History
</button>
<button
onClick={() => openParticipantsModal(case_.id)}
className="flex items-center gap-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<Users className="h-4 w-4" />
Participants
</button>
</>
)}
{session?.user?.role === 'LAWYER' && (
<button
onClick={() => handleOfferToRepresent(case_.id)}
className="flex items-center gap-2 px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700 transition-colors"
>
<Briefcase className="h-4 w-4" />
Offer to Represent
</button>
)}
</div>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
{/* Enhanced Competition Modal */}
{showCompetitionModal && selectedCase && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<h3 className="text-lg font-semibold mb-4">Place Your Bid</h3>
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
handlePlaceBid(
selectedCase.id,
parseFloat(formData.get('bidAmount') as string),
formData.get('message') as string
);
}}>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Bid Amount (CAD)
</label>
<input
type="number"
name="bidAmount"
min={selectedCase.minimumBid || 0}
step="0.01"
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
placeholder={`Min: $${selectedCase.minimumBid || 0}`}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Your Proposal
</label>
<textarea
name="message"
rows={4}
required
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
placeholder="Explain why you're the best lawyer for this case..."
/>
</div>
</div>
<div className="flex gap-3 mt-6">
<button
type="submit"
className="flex-1 bg-purple-600 text-white py-2 rounded-lg hover:bg-purple-700"
>
Place Bid
</button>
<button
type="button"
onClick={() => setShowCompetitionModal(false)}
className="flex-1 border border-gray-300 text-gray-700 py-2 rounded-lg hover:bg-gray-50"
>
Cancel
</button>
</div>
</form>
</div>
</div>
)}
{/* Offer Modal */}
{showOfferModal && selectedCase && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<h3 className="text-xl font-bold mb-4">Offer to Represent</h3>
<p className="text-gray-600 mb-4">Case: {selectedCase.title}</p>
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
submitOffer({
message: formData.get('message') as string,
proposedRate: formData.get('rate') ? Number(formData.get('rate')) : undefined,
estimatedHours: formData.get('hours') ? Number(formData.get('hours')) : undefined
});
}}>
<div className="space-y-4">
<textarea
name="message"
placeholder="Why are you the right lawyer for this case?"
rows={4}
className="w-full border rounded-lg p-3 focus:ring-2 focus:ring-blue-500"
required
/>
<div className="grid grid-cols-2 gap-4">
<input
name="rate"
type="number"
placeholder="Hourly rate ($)"
className="border rounded-lg p-3 focus:ring-2 focus:ring-blue-500"
/>
<input
name="hours"
type="number"
placeholder="Estimated hours"
className="border rounded-lg p-3 focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div className="flex gap-3 mt-6">
<button
type="submit"
className="flex-1 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700"
>
Submit Offer
</button>
<button
type="button"
onClick={() => setShowOfferModal(false)}
className="flex-1 border border-gray-300 text-gray-700 py-2 rounded-lg hover:bg-gray-50"
>
Cancel
</button>
</div>
</form>
</div>
</div>
)}
{/* Bid History Modal */}
{showBidHistoryModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-2xl w-full mx-4">
<h3 className="text-lg font-semibold mb-4">Bid History</h3>
<div className="max-h-96 overflow-y-auto">
{bidHistory.length === 0 ? (
<div className="text-gray-500">No bids yet.</div>
) : (
<table className="w-full text-sm">
<thead>
<tr className="border-b">
<th className="text-left py-2">Lawyer</th>
<th className="text-left py-2">Bid Amount</th>
<th className="text-left py-2">Message</th>
<th className="text-left py-2">Time</th>
</tr>
</thead>
<tbody>
{bidHistory.map((bid, idx) => (
<tr key={bid.id} className={idx % 2 === 0 ? 'bg-gray-50' : ''}>
<td className="py-2 flex items-center gap-2">
{bid.lawyer.profilePicture ? (
<img src={bid.lawyer.profilePicture} alt={bid.lawyer.name} className="w-6 h-6 rounded-full" />
) : (
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center text-white text-xs font-medium">
{bid.lawyer.name.charAt(0)}
</div>
)}
<span>{bid.lawyer.name}</span>
</td>
<td className="py-2 font-semibold">${bid.bidAmount.toLocaleString()}</td>
<td className="py-2">{bid.message}</td>
<td className="py-2">{new Date(bid.createdAt).toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
<div className="flex justify-end mt-6">
<button
onClick={() => setShowBidHistoryModal(false)}
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50"
>
Close
</button>
</div>
</div>
</div>
)}
{/* Participants Modal */}
{showParticipantsModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-lg w-full mx-4">
<h3 className="text-lg font-semibold mb-4">Competition Participants</h3>
<div className="max-h-96 overflow-y-auto">
{participants.length === 0 ? (
<div className="text-gray-500">No participants yet.</div>
) : (
<ul className="space-y-3">
{participants.map((p) => (
<li key={p.id} className="flex items-center gap-3">
{p.lawyer.profilePicture ? (
<img src={p.lawyer.profilePicture} alt={p.lawyer.name} className="w-8 h-8 rounded-full" />
) : (
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-medium">
{p.lawyer.name.charAt(0)}
</div>
)}
<div>
<div className="font-medium text-gray-900">{p.lawyer.name}</div>
<div className="text-xs text-gray-500">{p.lawyer.specialization}</div>
</div>
{p.lawyer.isVerified && (
<Badge className="h-4 w-4 text-blue-600 ml-2" />
)}
</li>
))}
</ul>
)}
</div>
<div className="flex justify-end mt-6">
<button
onClick={() => setShowParticipantsModal(false)}
className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50"
>
Close
</button>
</div>
</div>
</div>
)}
{/* Empty State */}
{!loading && cases.length === 0 && (
<div className="text-center py-12">
<Scale className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-xl font-semibold text-gray-900 mb-2">No cases found</h3>
<p className="text-gray-600">
{searchTerm ? 'Try adjusting your search terms' : 'No public cases available at the moment'}
</p>
</div>
)}
</div>
);
};
export default PublicCaseFeed;