mad-lawsuit/frontend/src/app/page.tsx
2025-06-25 17:52:58 -05:00

564 lines
20 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { Mail, FileText, ChevronDown, ChevronUp } from 'lucide-react';
import PDFViewer from '../components/PDFViewer';
interface Document {
id: number;
title: string;
summary?: string;
notes?: string;
displayOrder: number;
}
interface DocketEntry {
id: number;
date: string;
title: string;
summary: string;
notes?: string;
documents: Document[];
}
export default function HomePage() {
const [docketEntries, setDocketEntries] = useState<DocketEntry[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [expandedEntries, setExpandedEntries] = useState<Set<number>>(new Set());
const [email, setEmail] = useState('');
const [subscribing, setSubscribing] = useState(false);
const [subscribeMessage, setSubscribeMessage] = useState('');
const [selectedDocument, setSelectedDocument] = useState<{
id: number;
title: string;
url: string;
} | null>(null);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
fetchDocketEntries();
}, []);
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
const fetchDocketEntries = async () => {
try {
const response = await fetch('/api/docket-entries');
if (!response.ok) {
throw new Error('Failed to fetch docket entries');
}
const data = await response.json();
setDocketEntries(data.entries || []);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
const toggleEntry = (entryId: number) => {
const newExpanded = new Set(expandedEntries);
if (newExpanded.has(entryId)) {
newExpanded.delete(entryId);
} else {
newExpanded.add(entryId);
}
setExpandedEntries(newExpanded);
};
const handleSubscribe = async (e: React.FormEvent) => {
e.preventDefault();
setSubscribing(true);
setSubscribeMessage('');
try {
const response = await fetch('/api/subscriptions/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
const data = await response.json();
if (response.ok) {
setSubscribeMessage('Successfully subscribed! You will receive notifications for new filings.');
setEmail('');
} else {
setSubscribeMessage(data.message || 'Failed to subscribe. Please try again.');
}
} catch (err) {
setSubscribeMessage('Failed to subscribe. Please try again.');
} finally {
setSubscribing(false);
}
};
const formatDate = (dateString: string) => {
// Extract date parts directly to avoid timezone conversion issues
const datePart = dateString.split('T')[0]; // Get just YYYY-MM-DD part
if (!datePart) return 'Invalid Date';
const parts = datePart.split('-');
if (parts.length !== 3) return 'Invalid Date';
const [year, month, day] = parts;
if (!year || !month || !day) return 'Invalid Date';
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
const getLastUpdated = () => {
if (docketEntries.length === 0) return 'No entries';
const latest = docketEntries.length > 0 ? docketEntries[docketEntries.length - 1] : null;
return latest ? formatDate(latest.date) : 'No entries';
};
const handleDocumentClick = (doc: Document) => {
const documentUrl = `/api/documents/${doc.id}/download`;
if (isMobile) {
// On mobile, open PDF directly in new tab (MCDI approach)
window.open(documentUrl, '_blank');
} else {
// On desktop, use modal viewer
setSelectedDocument({
id: doc.id,
title: doc.title,
url: documentUrl
});
}
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-white">
<div className="text-center">
<div className="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-gray-600">Loading court documents...</p>
</div>
</div>
);
}
return (
<div style={{
width: '100%',
display: 'block',
textAlign: 'center',
margin: '0 auto',
backgroundColor: '#6E6362',
minHeight: '100vh'
}}>
{/* Centered Header */}
<header style={{
width: '100%',
padding: '2rem 0',
backgroundColor: 'rgba(57, 64, 83, 0.95)',
borderBottom: '1px solid #4E4A59',
textAlign: 'center',
display: 'block'
}}>
<div style={{
width: '100%',
textAlign: 'center',
display: 'block',
margin: '0 auto'
}}>
<h1 style={{
fontSize: '2.5rem',
fontWeight: 'bold',
color: 'white',
marginBottom: '0.5rem',
textAlign: 'center',
display: 'block',
width: '100%',
lineHeight: '1.2'
}}>
ELIZABETH KRAGH<br />
v.<br />
MONTANA ASSOCIATION OF THE DEAF
</h1>
<p style={{
color: '#7CAE7A',
marginBottom: '2rem',
textAlign: 'center',
display: 'block',
width: '100%'
}}>Court Docket & Legal Documents</p>
<div style={{
textAlign: 'center',
display: 'block',
width: '100%'
}}>
<h3 style={{
fontSize: '1.125rem',
fontWeight: '500',
color: 'white',
marginBottom: '1rem',
textAlign: 'center',
display: 'block',
width: '100%'
}}>Stay Informed</h3>
<div style={{
display: 'flex',
justifyContent: 'center',
marginBottom: '2rem',
width: '100%'
}}>
<form onSubmit={handleSubscribe} style={{ display: 'flex', gap: '0.5rem', maxWidth: '28rem' }}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email address"
style={{
flex: '1',
padding: '0.5rem 0.75rem',
fontSize: '0.875rem',
border: '1px solid #d1d5db',
borderRadius: '0.375rem'
}}
required
/>
<button
type="submit"
disabled={subscribing}
style={{
backgroundColor: '#7CAE7A',
color: 'white',
padding: '0.5rem 1rem',
borderRadius: '0.375rem',
fontSize: '0.875rem',
fontWeight: '500',
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
border: 'none',
cursor: 'pointer'
}}
>
<Mail className="w-4 h-4" />
{subscribing ? 'Subscribing...' : 'Subscribe to Updates'}
</button>
</form>
</div>
{subscribeMessage && (
<p style={{
marginTop: '0.5rem',
fontSize: '0.875rem',
textAlign: 'center',
color: subscribeMessage.includes('Successfully') ? '#059669' : '#dc2626'
}}>
{subscribeMessage}
</p>
)}
</div>
</div>
</header>
{/* Centered Case Information */}
<section style={{
width: '100%',
padding: '2rem 0',
textAlign: 'center',
display: 'block'
}}>
<div style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}>
<div style={{
backgroundColor: 'white',
border: '1px solid #e5e7eb',
borderRadius: '0.5rem',
padding: '1.5rem',
textAlign: 'center',
maxWidth: '28rem',
margin: '0 auto'
}}>
<h2 style={{
fontSize: '1.25rem',
fontWeight: '600',
color: '#111827',
marginBottom: '1.5rem',
textAlign: 'center',
width: '100%'
}}>Case Information</h2>
<div style={{ textAlign: 'center', marginBottom: '1rem' }}>
<div style={{ fontSize: '0.875rem', fontWeight: '500', color: '#6b7280', marginBottom: '0.25rem', textAlign: 'center' }}>Case Title</div>
<div style={{ fontSize: '1rem', fontWeight: '500', color: '#111827', textAlign: 'center' }}>Elizabeth Kragh v. Montana Association of the Deaf</div>
</div>
<div style={{ textAlign: 'center', marginBottom: '1rem' }}>
<div style={{ fontSize: '0.875rem', fontWeight: '500', color: '#6b7280', marginBottom: '0.25rem', textAlign: 'center' }}>Status</div>
<div style={{ textAlign: 'center' }}>
<span style={{
display: 'inline-block',
padding: '0.25rem 0.75rem',
borderRadius: '9999px',
fontSize: '0.875rem',
fontWeight: '500',
backgroundColor: '#dcfce7',
color: '#166534',
textAlign: 'center'
}}>
Active Litigation
</span>
</div>
</div>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '0.875rem', fontWeight: '500', color: '#6b7280', marginBottom: '0.25rem', textAlign: 'center' }}>Last Updated</div>
<div style={{ fontSize: '1rem', fontWeight: '500', color: '#111827', textAlign: 'center' }}>{getLastUpdated()}</div>
</div>
</div>
</div>
</section>
{/* Centered Court Docket Entries */}
<section style={{
width: '100%',
padding: '2rem 0',
textAlign: 'center',
display: 'block'
}}>
<div style={{
textAlign: 'center',
width: '100%'
}}>
<h2 style={{
fontSize: '1.5rem',
fontWeight: '600',
color: 'white',
marginBottom: '0.5rem',
textAlign: 'center',
display: 'block',
width: '100%'
}}>Court Docket Entries</h2>
<p style={{
color: 'white',
marginBottom: '2rem',
textAlign: 'center',
display: 'block',
width: '100%'
}}>Chronological record of all court filings and proceedings</p>
{error ? (
<div className="bg-white border border-gray-200 rounded-lg p-8 text-center max-w-md">
<div className="w-12 h-12 mx-auto mb-4 rounded-full bg-red-50 flex items-center justify-center">
<FileText className="w-6 h-6 text-red-500" />
</div>
<p className="text-red-600 font-medium mb-4">{error}</p>
<button
onClick={fetchDocketEntries}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors"
>
Try Again
</button>
</div>
) : docketEntries.length === 0 ? (
<div className="bg-white border border-gray-200 rounded-lg p-12 text-center max-w-md">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-50 flex items-center justify-center">
<FileText className="w-8 h-8 text-gray-300" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">No Docket Entries</h3>
<p className="text-gray-600">No court documents have been filed yet.</p>
</div>
) : (
<div style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '0 1rem'
}}>
{docketEntries.map((entry, index) => (
<div
key={entry.id}
style={{
width: '75%',
maxWidth: '800px',
minHeight: '120px',
marginBottom: '8px',
backgroundColor: 'white',
border: '2px solid #d1d5db',
borderRadius: '0.5rem',
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
overflow: 'hidden'
}}
>
<button
onClick={() => toggleEntry(entry.id)}
style={{
width: '100%',
height: '120px',
padding: '1.5rem 1.5rem 1rem 1.5rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: 'transparent',
border: 'none',
textAlign: 'left',
cursor: 'pointer',
transition: 'background-color 0.2s'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flex: 1 }}>
<div style={{
width: '2rem',
height: '2rem',
borderRadius: '50%',
backgroundColor: '#394053',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontWeight: 'bold',
fontSize: '0.875rem',
flexShrink: 0
}}>
{index + 1}
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ marginBottom: '0.5rem' }}>
<div style={{ fontSize: '0.875rem', color: '#6b7280', marginBottom: '0.25rem' }}>
{formatDate(entry.date)}
</div>
</div>
<h3 style={{
fontSize: '1rem',
fontWeight: '500',
color: '#111827',
lineHeight: '1.5',
overflow: 'hidden',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical'
}}>
{entry.title || 'No court filing title available.'}
</h3>
</div>
</div>
<div style={{ flexShrink: 0, marginLeft: '1rem' }}>
{expandedEntries.has(entry.id) ? (
<ChevronUp style={{ width: '1.25rem', height: '1.25rem', color: '#9ca3af' }} />
) : (
<ChevronDown style={{ width: '1.25rem', height: '1.25rem', color: '#9ca3af' }} />
)}
</div>
</button>
{/* Expanded Content */}
{expandedEntries.has(entry.id) && (
<div className="px-6 pb-6 border-t border-gray-100">
{entry.summary && (
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
<p className="text-sm text-blue-900 font-medium">Brief Summary:</p>
<p className="text-sm text-blue-900 mt-1">{entry.summary}</p>
</div>
)}
<div className="mt-6">
<div className="space-y-3">
{entry.documents
.sort((a, b) => a.displayOrder - b.displayOrder)
.map((doc) => (
<div key={doc.id} className="flex items-center justify-center p-3 bg-gray-50 rounded-lg">
<button
onClick={() => handleDocumentClick(doc)}
style={{
display: 'inline-flex',
alignItems: 'center',
padding: '0.375rem 0.75rem',
backgroundColor: '#7CAE7A',
color: 'white',
borderRadius: '0.375rem',
fontSize: '0.875rem',
fontWeight: '500',
border: 'none',
cursor: 'pointer',
transition: 'background-color 0.2s'
}}
onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#6b9c69'}
onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#7CAE7A'}
>
<FileText style={{ width: '1rem', height: '1rem', marginRight: '0.25rem' }} />
View PDF
</button>
</div>
))}
</div>
</div>
</div>
)}
</div>
))}
</div>
)}
</div>
</section>
{/* PDF Viewer Modal */}
{selectedDocument && (
<PDFViewer
isOpen={!!selectedDocument}
onClose={() => setSelectedDocument(null)}
documentTitle={selectedDocument.title}
documentUrl={selectedDocument.url}
/>
)}
{/* Footer */}
<footer style={{
width: '100%',
padding: '2rem 0',
backgroundColor: 'rgba(57, 64, 83, 0.95)',
borderTop: '1px solid #4E4A59',
textAlign: 'center',
display: 'block'
}}>
<div style={{
textAlign: 'center',
display: 'block',
width: '100%'
}}>
<span style={{ color: '#9ca3af' }}>Designed by </span>
<a
href="http://deafgain.org"
target="_blank"
rel="noopener noreferrer"
style={{
color: '#fbbf24',
textDecoration: 'none',
transition: 'color 0.2s'
}}
onMouseEnter={(e) => e.currentTarget.style.color = '#f59e0b'}
onMouseLeave={(e) => e.currentTarget.style.color = '#fbbf24'}
>
DeafGain LLC
</a>
</div>
</footer>
</div>
);
}