← back to Sdcc Awards
cypressaward/frontend/app/browse-opportunities/page.tsx
381 lines
'use client'
import { useState, useEffect } from 'react'
import { Card } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Calendar, DollarSign, Building, Clock, TrendingUp, AlertCircle, Search } from 'lucide-react'
import { supabase } from '@/lib/supabase'
interface Opportunity {
id: number
case_name: string
court: string
settlement_amount: number
estimated_cypres: number
deadline: string
status: 'Open' | 'Pending' | 'Closed'
category: string
description: string
eligibility: string[]
law_firm: string
}
export default function BrowseOpportunitiesPage() {
const [opportunities, setOpportunities] = useState<Opportunity[]>([])
const [filteredOpps, setFilteredOpps] = useState<Opportunity[]>([])
const [loading, setLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState('')
const [selectedCategory, setSelectedCategory] = useState('all')
const [selectedStatus, setSelectedStatus] = useState('all')
// Mock data for opportunities
const mockOpportunities: Opportunity[] = [
{
id: 1,
case_name: 'In re: Meta Pixel Privacy Litigation',
court: 'N.D. Cal.',
settlement_amount: 90000000,
estimated_cypres: 15000000,
deadline: '2024-12-31',
status: 'Open',
category: 'Privacy & Data Security',
description: 'Settlement regarding unauthorized data collection through Meta Pixel tracking technology',
eligibility: ['Privacy advocacy organizations', 'Consumer protection nonprofits', 'Digital rights groups'],
law_firm: 'Lieff Cabraser Heimann & Bernstein'
},
{
id: 2,
case_name: 'Wells Fargo Overdraft Fee Settlement',
court: 'S.D.N.Y.',
settlement_amount: 75000000,
estimated_cypres: 8000000,
deadline: '2024-11-15',
status: 'Open',
category: 'Consumer Protection',
description: 'Class action settlement for improper overdraft fee practices',
eligibility: ['Financial literacy organizations', 'Consumer advocacy groups', 'Legal aid societies'],
law_firm: 'Berger Montague'
},
{
id: 3,
case_name: 'Student Loan Servicing Practices',
court: 'E.D. Pa.',
settlement_amount: 55000000,
estimated_cypres: 12000000,
deadline: '2025-01-20',
status: 'Open',
category: 'Education & Student Rights',
description: 'Settlement addressing deceptive student loan servicing practices',
eligibility: ['Student advocacy organizations', 'Education nonprofits', 'Debt counseling services'],
law_firm: 'Cohen Milstein'
},
{
id: 4,
case_name: 'Healthcare Data Breach Settlement',
court: 'C.D. Cal.',
settlement_amount: 120000000,
estimated_cypres: 25000000,
deadline: '2024-10-30',
status: 'Pending',
category: 'Healthcare',
description: 'Major healthcare system data breach affecting millions of patients',
eligibility: ['Healthcare advocacy groups', 'Patient rights organizations', 'Privacy nonprofits'],
law_firm: 'Keller Rohrback'
},
{
id: 5,
case_name: 'Environmental Contamination Settlement',
court: 'D. N.J.',
settlement_amount: 200000000,
estimated_cypres: 35000000,
deadline: '2025-02-28',
status: 'Open',
category: 'Environmental Protection',
description: 'Settlement for groundwater contamination affecting local communities',
eligibility: ['Environmental nonprofits', 'Community health organizations', 'Clean water advocacy groups'],
law_firm: 'Weitz & Luxenberg'
},
{
id: 6,
case_name: 'Workplace Discrimination Class Action',
court: 'N.D. Ill.',
settlement_amount: 45000000,
estimated_cypres: 6000000,
deadline: '2024-09-15',
status: 'Closed',
category: 'Workers Rights',
description: 'Settlement addressing systemic workplace discrimination',
eligibility: ['Workers rights organizations', 'Civil rights groups', 'Employment law clinics'],
law_firm: 'Sanford Heisler Sharp'
}
]
useEffect(() => {
// In production, fetch from Supabase
setOpportunities(mockOpportunities)
setFilteredOpps(mockOpportunities)
setLoading(false)
}, [])
useEffect(() => {
let filtered = opportunities
// Search filter
if (searchTerm) {
filtered = filtered.filter(opp =>
opp.case_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
opp.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
opp.category.toLowerCase().includes(searchTerm.toLowerCase())
)
}
// Category filter
if (selectedCategory !== 'all') {
filtered = filtered.filter(opp => opp.category === selectedCategory)
}
// Status filter
if (selectedStatus !== 'all') {
filtered = filtered.filter(opp => opp.status === selectedStatus)
}
setFilteredOpps(filtered)
}, [opportunities, searchTerm, selectedCategory, selectedStatus])
const categories = Array.from(new Set(opportunities.map(o => o.category)))
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount)
}
const getDaysRemaining = (deadline: string) => {
const today = new Date()
const deadlineDate = new Date(deadline)
const diffTime = deadlineDate.getTime() - today.getTime()
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays
}
const getStatusColor = (status: string) => {
switch(status) {
case 'Open': return 'bg-green-100 text-green-800'
case 'Pending': return 'bg-yellow-100 text-yellow-800'
case 'Closed': return 'bg-gray-100 text-gray-800'
default: return 'bg-gray-100 text-gray-800'
}
}
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="max-w-7xl mx-auto px-4">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold mb-4">Browse Cy Pres Opportunities</h1>
<p className="text-xl text-gray-600">
Active and upcoming cy pres distributions from class action settlements
</p>
</div>
{/* Stats Cards */}
<div className="grid md:grid-cols-4 gap-4 mb-8">
<Card className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Active Opportunities</p>
<p className="text-2xl font-bold">{opportunities.filter(o => o.status === 'Open').length}</p>
</div>
<TrendingUp className="h-8 w-8 text-green-500" />
</div>
</Card>
<Card className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Total Available</p>
<p className="text-2xl font-bold">
{formatCurrency(opportunities.reduce((sum, o) => sum + o.estimated_cypres, 0))}
</p>
</div>
<DollarSign className="h-8 w-8 text-blue-500" />
</div>
</Card>
<Card className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Closing Soon</p>
<p className="text-2xl font-bold">
{opportunities.filter(o => getDaysRemaining(o.deadline) <= 30 && o.status === 'Open').length}
</p>
</div>
<Clock className="h-8 w-8 text-orange-500" />
</div>
</Card>
<Card className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600">Categories</p>
<p className="text-2xl font-bold">{categories.length}</p>
</div>
<Building className="h-8 w-8 text-purple-500" />
</div>
</Card>
</div>
{/* Filters */}
<Card className="p-6 mb-8">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
<input
type="text"
placeholder="Search opportunities..."
className="w-full pl-10 pr-4 py-2 border rounded-md"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<select
className="px-4 py-2 border rounded-md"
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
>
<option value="all">All Categories</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
<select
className="px-4 py-2 border rounded-md"
value={selectedStatus}
onChange={(e) => setSelectedStatus(e.target.value)}
>
<option value="all">All Status</option>
<option value="Open">Open</option>
<option value="Pending">Pending</option>
<option value="Closed">Closed</option>
</select>
</div>
</Card>
{/* Opportunities List */}
<div className="space-y-6">
{loading ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
</div>
) : filteredOpps.length === 0 ? (
<Card className="p-12 text-center">
<AlertCircle className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-600">No opportunities match your search criteria</p>
</Card>
) : (
filteredOpps.map(opp => {
const daysRemaining = getDaysRemaining(opp.deadline)
return (
<Card key={opp.id} className="p-6 hover:shadow-lg transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<Badge className={getStatusColor(opp.status)}>
{opp.status}
</Badge>
<Badge variant="outline">{opp.category}</Badge>
{daysRemaining <= 30 && opp.status === 'Open' && (
<Badge className="bg-orange-100 text-orange-800">
<Clock className="h-3 w-3 mr-1" />
{daysRemaining} days left
</Badge>
)}
</div>
<h3 className="text-xl font-bold mb-2">{opp.case_name}</h3>
<p className="text-gray-600 mb-3">{opp.description}</p>
<div className="grid md:grid-cols-3 gap-4 mb-4">
<div>
<p className="text-sm text-gray-500">Court</p>
<p className="font-medium">{opp.court}</p>
</div>
<div>
<p className="text-sm text-gray-500">Total Settlement</p>
<p className="font-medium">{formatCurrency(opp.settlement_amount)}</p>
</div>
<div>
<p className="text-sm text-gray-500">Est. Cy Pres Amount</p>
<p className="font-bold text-green-600">{formatCurrency(opp.estimated_cypres)}</p>
</div>
</div>
<div className="mb-4">
<p className="text-sm text-gray-500 mb-2">Eligible Organizations:</p>
<div className="flex flex-wrap gap-2">
{opp.eligibility.map((elig, idx) => (
<Badge key={idx} variant="secondary" className="text-xs">
{elig}
</Badge>
))}
</div>
</div>
<div className="flex items-center justify-between">
<div className="text-sm text-gray-500">
Law Firm: <span className="font-medium text-gray-700">{opp.law_firm}</span>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm">
View Details
</Button>
{opp.status === 'Open' && (
<Button size="sm">
Apply Now
</Button>
)}
</div>
</div>
</div>
</div>
{opp.status === 'Open' && (
<div className="mt-4 pt-4 border-t">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-600">
Application Deadline: <span className="font-medium">{new Date(opp.deadline).toLocaleDateString()}</span>
</span>
{daysRemaining > 0 && (
<span className={`font-medium ${daysRemaining <= 7 ? 'text-red-600' : 'text-gray-600'}`}>
{daysRemaining} days remaining
</span>
)}
</div>
</div>
)}
</Card>
)
})
)}
</div>
{/* Help Section */}
<Card className="mt-12 p-8 bg-blue-50">
<h2 className="text-2xl font-bold mb-4">Need Help Applying?</h2>
<p className="text-gray-700 mb-4">
Our team can guide you through the cy pres application process and help maximize your chances of receiving funding.
</p>
<div className="flex gap-4">
<Button>
Schedule Consultation
</Button>
<Button variant="outline">
Download Application Guide
</Button>
</div>
</Card>
</div>
</div>
)
}