![]() 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/pages/client/ |
'use client';
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import { isSuperAdmin } from '@/lib/auth-utils';
import {
FileText,
DollarSign,
MessageSquare,
Calendar,
AlertCircle,
CheckCircle,
Clock,
Download,
Upload,
Eye,
Edit,
Send,
Bell,
Settings,
User,
Shield,
TrendingUp,
Activity,
Folder,
Phone,
Mail,
Video,
MapPin,
Star,
MoreVertical,
Plus,
Trash2
} from 'lucide-react';
import { format } from 'date-fns';
import CaseWidget from '@/components/CaseWidget';
import LayoutWithSidebar from '../../components/LayoutWithSidebar';
// Utility functions
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'active': return 'text-green-600 bg-green-100';
case 'pending': return 'text-yellow-600 bg-yellow-100';
case 'completed': return 'text-blue-600 bg-blue-100';
case 'cancelled': return 'text-red-600 bg-red-100';
default: return 'text-gray-600 bg-gray-100';
}
};
const getPriorityColor = (priority: string) => {
switch (priority.toLowerCase()) {
case 'high': return 'text-red-600 bg-red-100';
case 'medium': return 'text-yellow-600 bg-yellow-100';
case 'low': return 'text-green-600 bg-green-100';
default: return 'text-gray-600 bg-gray-100';
}
};
interface Case {
id: string;
caseNumber: string;
title: string;
status: string;
priority: string;
createdAt: string;
updatedAt: string;
description?: string;
lawyer: {
id: string;
name: string;
email: string;
profile?: {
phone?: string;
avatar?: string;
};
};
documents: Document[];
payments: Payment[];
updates: CaseUpdate[];
}
interface Document {
id: string;
title: string;
type: string;
filePath: string;
fileSize: number;
uploadedAt: string;
uploadedBy: string;
status: string;
}
interface Payment {
id: string;
amount: number;
status: string;
dueDate: string;
paidAt?: string;
description: string;
type: string;
}
interface CaseUpdate {
id: string;
title: string;
content: string;
type: string;
createdAt: string;
createdBy: string;
}
interface Message {
id: string;
content: string;
senderId: string;
senderName: string;
timestamp: string;
isRead: boolean;
}
const ClientDashboard: React.FC = () => {
const { data: session, status } = useSession();
const router = useRouter();
const [cases, setCases] = useState<Case[]>([]);
const [selectedCase, setSelectedCase] = useState<Case | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'overview' | 'cases' | 'documents' | 'payments' | 'messages' | 'applications'>('overview');
const [showUploadModal, setShowUploadModal] = useState(false);
const [showMessageModal, setShowMessageModal] = useState(false);
useEffect(() => {
if (status === 'loading') return;
if (!session?.user) {
router.push('/auth/login');
return;
}
// Check if user is a client, user, or super admin
if (!['CLIENT', 'USER'].includes(session.user.role) && !isSuperAdmin(session)) {
router.push('/dashboard');
return;
}
fetchClientData();
}, [session, status]);
const fetchClientData = async () => {
try {
setLoading(true);
// Fetch cases
const casesResponse = await fetch('/api/client/cases');
if (casesResponse.ok) {
const casesData = await casesResponse.json();
setCases(casesData.cases || []);
if (casesData.cases?.length > 0) {
setSelectedCase(casesData.cases[0]);
}
}
// Fetch messages
const messagesResponse = await fetch('/api/client/messages');
if (messagesResponse.ok) {
const messagesData = await messagesResponse.json();
setMessages(messagesData.messages || []);
}
} catch (error) {
console.error('Error fetching client data:', error);
} finally {
setLoading(false);
}
};
const handleDocumentUpload = async (file: File, caseId: string) => {
try {
const formData = new FormData();
formData.append('file', file);
formData.append('caseId', caseId);
formData.append('title', file.name);
const response = await fetch('/api/client/documents/upload', {
method: 'POST',
body: formData
});
if (response.ok) {
fetchClientData();
setShowUploadModal(false);
}
} catch (error) {
console.error('Error uploading document:', error);
}
};
const handleSendMessage = async (content: string, recipientId: string) => {
try {
const response = await fetch('/api/client/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content,
recipientId,
caseId: selectedCase?.id
})
});
if (response.ok) {
fetchClientData();
setShowMessageModal(false);
}
} catch (error) {
console.error('Error sending message:', error);
}
};
if (loading) {
return (
<LayoutWithSidebar>
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
</LayoutWithSidebar>
);
}
return (
<LayoutWithSidebar>
<div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">Client Dashboard</h1>
<p className="text-gray-600 mt-2">Welcome back, {session?.user?.name}</p>
</div>
<div className="flex items-center gap-4">
<button className="p-2 text-gray-600 hover:bg-gray-100 rounded-full">
<Bell className="w-5 h-5" />
</button>
<button className="p-2 text-gray-600 hover:bg-gray-100 rounded-full">
<Settings className="w-5 h-5" />
</button>
</div>
</div>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-blue-100 rounded-lg">
<FileText className="w-6 h-6 text-blue-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Active Cases</p>
<p className="text-2xl font-bold text-gray-900">{cases.filter(c => c.status === 'ACTIVE').length}</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-green-100 rounded-lg">
<DollarSign className="w-6 h-6 text-green-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Total Paid</p>
<p className="text-2xl font-bold text-gray-900">
${cases.reduce((total, c) =>
total + (c.payments ? c.payments.filter(p => p.status === 'PAID').reduce((sum, p) => sum + p.amount, 0) : 0), 0
).toLocaleString()}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-yellow-100 rounded-lg">
<Clock className="w-6 h-6 text-yellow-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Pending Payments</p>
<p className="text-2xl font-bold text-gray-900">
${cases.reduce((total, c) =>
total + c.payments.filter(p => p.status === 'PENDING').reduce((sum, p) => sum + p.amount, 0), 0
).toLocaleString()}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-purple-100 rounded-lg">
<MessageSquare className="w-6 h-6 text-purple-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Unread Messages</p>
<p className="text-2xl font-bold text-gray-900">{messages.filter(m => !m.isRead).length}</p>
</div>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Quick Actions</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<button
onClick={() => router.push('/admin/cases')}
className="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
<FileText className="h-6 w-6 text-blue-600 mr-3" />
<div className="text-left">
<div className="font-medium text-gray-900">Case Management</div>
<div className="text-sm text-gray-500">View all cases</div>
</div>
</button>
<button
onClick={() => router.push('/hire/new-case')}
className="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
<Plus className="h-6 w-6 text-green-600 mr-3" />
<div className="text-left">
<div className="font-medium text-gray-900">Create New Case</div>
<div className="text-sm text-gray-500">Start legal process</div>
</div>
</button>
<button
onClick={() => router.push('/messages')}
className="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
<MessageSquare className="h-6 w-6 text-purple-600 mr-3" />
<div className="text-left">
<div className="font-medium text-gray-900">Messages</div>
<div className="text-sm text-gray-500">View communications</div>
</div>
</button>
</div>
</div>
{/* Main Content */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
{/* Sidebar */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Navigation</h3>
<nav className="space-y-2">
<button
onClick={() => setActiveTab('overview')}
className={`w-full flex items-center px-3 py-2 rounded-md text-sm font-medium ${
activeTab === 'overview'
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
<Activity className="w-4 h-4 mr-3" />
Overview
</button>
<button
onClick={() => setActiveTab('cases')}
className={`w-full flex items-center px-3 py-2 rounded-md text-sm font-medium ${
activeTab === 'cases'
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
<FileText className="w-4 h-4 mr-3" />
My Cases
</button>
<button
onClick={() => setActiveTab('documents')}
className={`w-full flex items-center px-3 py-2 rounded-md text-sm font-medium ${
activeTab === 'documents'
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
<Folder className="w-4 h-4 mr-3" />
Documents
</button>
<button
onClick={() => setActiveTab('payments')}
className={`w-full flex items-center px-3 py-2 rounded-md text-sm font-medium ${
activeTab === 'payments'
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
<DollarSign className="w-4 h-4 mr-3" />
Payments
</button>
<button
onClick={() => setActiveTab('messages')}
className={`w-full flex items-center px-3 py-2 rounded-md text-sm font-medium ${
activeTab === 'messages'
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
<MessageSquare className="w-4 h-4 mr-3" />
Messages
</button>
<button
onClick={() => setActiveTab('applications')}
className={`w-full flex items-center px-3 py-2 rounded-md text-sm font-medium ${
activeTab === 'applications'
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-50'
}`}
>
<FileText className="w-4 h-4 mr-3" />
Applications
</button>
</nav>
{/* Lawyer Contact */}
{selectedCase && (
<div className="mt-8 pt-6 border-t border-gray-200">
<h4 className="text-sm font-medium text-gray-900 mb-3">Your Lawyer</h4>
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<User className="w-5 h-5 text-blue-600" />
</div>
<div>
<p className="text-sm font-medium text-gray-900">
{selectedCase.lawyer.name}
</p>
<p className="text-xs text-gray-500">{selectedCase.lawyer.email}</p>
</div>
</div>
<div className="mt-3 space-y-2">
<button className="w-full flex items-center justify-center px-3 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-md">
<Phone className="w-4 h-4 mr-2" />
Call
</button>
<button className="w-full flex items-center justify-center px-3 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-md">
<Mail className="w-4 h-4 mr-2" />
Email
</button>
<button className="w-full flex items-center justify-center px-3 py-2 text-sm text-blue-600 hover:bg-blue-50 rounded-md">
<Video className="w-4 h-4 mr-2" />
Video Call
</button>
</div>
</div>
)}
</div>
</div>
{/* Main Content Area */}
<div className="lg:col-span-3">
{activeTab === 'overview' && (
<OverviewTab cases={cases} selectedCase={selectedCase} />
)}
{activeTab === 'cases' && (
<CasesTab
cases={cases}
selectedCase={selectedCase}
onSelectCase={setSelectedCase}
/>
)}
{activeTab === 'documents' && (
<DocumentsTab
cases={cases}
selectedCase={selectedCase}
onUpload={() => setShowUploadModal(true)}
/>
)}
{activeTab === 'payments' && (
<PaymentsTab cases={cases} selectedCase={selectedCase} />
)}
{activeTab === 'messages' && (
<MessagesTab
messages={messages}
selectedCase={selectedCase}
onSendMessage={() => setShowMessageModal(true)}
/>
)}
{activeTab === 'applications' && (
<ApplicationsTab />
)}
</div>
</div>
</div>
{/* Upload Modal */}
{showUploadModal && (
<UploadModal
onUpload={handleDocumentUpload}
onClose={() => setShowUploadModal(false)}
caseId={selectedCase?.id}
/>
)}
{/* Message Modal */}
{showMessageModal && (
<MessageModal
onSend={handleSendMessage}
onClose={() => setShowMessageModal(false)}
recipientId={selectedCase?.lawyer.id}
/>
)}
</div>
</LayoutWithSidebar>
);
};
// Tab Components
const OverviewTab: React.FC<{ cases: Case[], selectedCase: Case | null }> = ({ cases, selectedCase }) => (
<div className="space-y-6">
{/* Welcome Section */}
<div className="bg-gradient-to-r from-blue-600 to-blue-700 rounded-lg shadow-sm p-6 text-white">
<h2 className="text-2xl font-bold mb-2">Welcome to Your Legal Dashboard</h2>
<p className="text-blue-100">
Manage your cases, track progress, and stay connected with your legal team.
</p>
</div>
{/* Unified Case Management Widget */}
<CaseWidget
title="My Cases"
showMyCases={true}
showAssignedCases={false}
showPublicCases={false}
maxCases={5}
showCreateButton={true}
showFilters={true}
className="mb-6"
/>
{/* Recent Activity */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Recent Activity</h2>
{selectedCase?.updates.slice(0, 5).map((update) => (
<div key={update.id} className="flex items-start space-x-3 py-3 border-b border-gray-100 last:border-b-0">
<div className="w-2 h-2 bg-blue-500 rounded-full mt-2"></div>
<div className="flex-1">
<p className="text-sm font-medium text-gray-900">{update.title}</p>
<p className="text-sm text-gray-600">{update.content}</p>
<p className="text-xs text-gray-500 mt-1">
{format(new Date(update.createdAt), 'MMM dd, yyyy')}
</p>
</div>
</div>
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Upcoming Deadlines</h3>
<div className="space-y-3">
{cases.flatMap(c => c.documents)
.filter(d => d.status === 'PENDING')
.slice(0, 3)
.map((doc) => (
<div key={doc.id} className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-900">{doc.title}</p>
<p className="text-xs text-gray-500">{doc.type}</p>
</div>
<span className="text-xs text-red-600 bg-red-100 px-2 py-1 rounded-full">
Due Soon
</span>
</div>
))}
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Recent Documents</h3>
<div className="space-y-3">
{cases.flatMap(c => c.documents)
.sort((a, b) => new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime())
.slice(0, 3)
.map((doc) => (
<div key={doc.id} className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<FileText className="w-4 h-4 text-gray-400" />
<div>
<p className="text-sm font-medium text-gray-900">{doc.title}</p>
<p className="text-xs text-gray-500">
{format(new Date(doc.uploadedAt), 'MMM dd, yyyy')}
</p>
</div>
</div>
<button className="text-blue-600 hover:text-blue-700">
<Download className="w-4 h-4" />
</button>
</div>
))}
</div>
</div>
</div>
</div>
);
const CasesTab: React.FC<{
cases: Case[],
selectedCase: Case | null,
onSelectCase: (case_: Case) => void
}> = ({ cases, selectedCase, onSelectCase }) => (
<div className="space-y-6">
{/* Case Management Header */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Case Management</h2>
<p className="text-gray-600 mt-1">Manage all your legal cases in one place</p>
</div>
<div className="flex items-center gap-3">
<span className="bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1 rounded-full">
{cases.length} Total Cases
</span>
</div>
</div>
</div>
{/* Unified Case Management Widget */}
<CaseWidget
title="All My Cases"
showMyCases={true}
showAssignedCases={false}
showPublicCases={false}
maxCases={10}
showCreateButton={true}
showFilters={true}
className="mb-6"
/>
{/* Case Statistics */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-green-100 rounded-lg">
<CheckCircle className="h-6 w-6 text-green-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Active Cases</p>
<p className="text-2xl font-bold text-gray-900">
{cases.filter(c => c.status === 'active').length}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-yellow-100 rounded-lg">
<Clock className="h-6 w-6 text-yellow-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Pending Cases</p>
<p className="text-2xl font-bold text-gray-900">
{cases.filter(c => c.status === 'pending').length}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-blue-100 rounded-lg">
<FileText className="h-6 w-6 text-blue-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Total Documents</p>
<p className="text-2xl font-bold text-gray-900">
{cases.reduce((total, c) => total + c.documents.length, 0)}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center">
<div className="p-2 bg-purple-100 rounded-lg">
<MessageSquare className="h-6 w-6 text-purple-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Recent Updates</p>
<p className="text-2xl font-bold text-gray-900">
{cases.reduce((total, c) => total + c.updates.length, 0)}
</p>
</div>
</div>
</div>
</div>
</div>
);
const DocumentsTab: React.FC<{
cases: Case[],
selectedCase: Case | null,
onUpload: () => void
}> = ({ cases, selectedCase, onUpload }) => (
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">Documents</h2>
<button
onClick={onUpload}
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
<Upload className="w-4 h-4 mr-2" />
Upload Document
</button>
</div>
</div>
<div className="p-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{cases.flatMap(c => c.documents).map((doc) => (
<div key={doc.id} className="border border-gray-200 rounded-lg p-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<h3 className="text-sm font-medium text-gray-900">{doc.title}</h3>
<p className="text-xs text-gray-500 mt-1">{doc.type}</p>
<p className="text-xs text-gray-500">
{format(new Date(doc.uploadedAt), 'MMM dd, yyyy')}
</p>
</div>
<div className="flex items-center space-x-2">
<button className="text-blue-600 hover:text-blue-700">
<Eye className="w-4 h-4" />
</button>
<button className="text-blue-600 hover:text-blue-700">
<Download className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
const PaymentsTab: React.FC<{ cases: Case[], selectedCase: Case | null }> = ({ cases }) => (
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="p-6 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-900">Payment History</h2>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Description
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Amount
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Due Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{cases.flatMap(c => c.payments).map((payment) => (
<tr key={payment.id}>
<td className="px-6 py-4 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">{payment.description}</div>
<div className="text-sm text-gray-500">{payment.type}</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
${payment.amount.toLocaleString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
payment.status === 'PAID' ? 'text-green-600 bg-green-100' :
payment.status === 'PENDING' ? 'text-yellow-600 bg-yellow-100' :
'text-red-600 bg-red-100'
}`}>
{payment.status}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{format(new Date(payment.dueDate), 'MMM dd, yyyy')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
{payment.status === 'PENDING' && (
<button className="text-blue-600 hover:text-blue-900">
Pay Now
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
const MessagesTab: React.FC<{
messages: Message[],
selectedCase: Case | null,
onSendMessage: () => void
}> = ({ messages, onSendMessage }) => (
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">Messages</h2>
<button
onClick={onSendMessage}
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
<Send className="w-4 h-4 mr-2" />
New Message
</button>
</div>
</div>
<div className="p-6">
<div className="space-y-4">
{messages.map((message) => (
<div key={message.id} className={`flex ${message.senderId === 'client' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${
message.senderId === 'client'
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-900'
}`}>
<p className="text-sm">{message.content}</p>
<p className={`text-xs mt-1 ${
message.senderId === 'client' ? 'text-blue-100' : 'text-gray-500'
}`}>
{format(new Date(message.timestamp), 'MMM dd, HH:mm')}
</p>
</div>
</div>
))}
</div>
</div>
</div>
);
// Modal Components
const UploadModal: React.FC<{
onUpload: (file: File, caseId: string) => void;
onClose: () => void;
caseId?: string;
}> = ({ onUpload, onClose, caseId }) => {
const [file, setFile] = useState<File | null>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (file && caseId) {
onUpload(file, caseId);
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Upload Document</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Select File
</label>
<input
type="file"
onChange={(e) => setFile(e.target.files?.[0] || null)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div className="flex items-center justify-end gap-4 pt-4">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:text-gray-800"
>
Cancel
</button>
<button
type="submit"
disabled={!file}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
Upload
</button>
</div>
</form>
</div>
</div>
</div>
);
};
const MessageModal: React.FC<{
onSend: (content: string, recipientId: string) => void;
onClose: () => void;
recipientId?: string;
}> = ({ onSend, onClose, recipientId }) => {
const [content, setContent] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (content && recipientId) {
onSend(content, recipientId);
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Send Message</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Message
</label>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Type your message..."
required
/>
</div>
<div className="flex items-center justify-end gap-4 pt-4">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:text-gray-800"
>
Cancel
</button>
<button
type="submit"
disabled={!content}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
Send
</button>
</div>
</form>
</div>
</div>
</div>
);
};
// Applications Tab Component
const ApplicationsTab: React.FC = () => {
const [registrations, setRegistrations] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRegistrations();
}, []);
const fetchRegistrations = async () => {
try {
setLoading(true);
const response = await fetch('/api/user/registrations');
if (response.ok) {
const data = await response.json();
setRegistrations(data.registrations || []);
}
} catch (error) {
console.error('Error fetching registrations:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
);
}
return (
<div className="space-y-6">
{/* Applications Header */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Application Management</h2>
<p className="text-gray-600 mt-1">Track and manage your legal applications</p>
</div>
<div className="flex items-center gap-3">
<span className="bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1 rounded-full">
{registrations.length} Total Applications
</span>
</div>
</div>
</div>
{/* Applications List */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="p-6 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900">Your Applications</h3>
</div>
{registrations.length === 0 ? (
<div className="p-8 text-center">
<FileText className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No Applications Yet</h3>
<p className="text-gray-600 mb-4">You haven't submitted any applications yet.</p>
<button
onClick={() => window.location.href = '/join'}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
Submit New Application
</button>
</div>
) : (
<div className="divide-y divide-gray-200">
{registrations.map((registration) => (
<div key={registration.id} className="p-6 hover:bg-gray-50 transition-colors">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h4 className="text-lg font-medium text-gray-900">
{registration.firstName} {registration.lastName}
</h4>
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
registration.status === 'APPROVED' ? 'bg-green-100 text-green-800' :
registration.status === 'REJECTED' ? 'bg-red-100 text-red-800' :
registration.status === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :
'bg-gray-100 text-gray-800'
}`}>
{registration.status}
</span>
</div>
<p className="text-sm text-gray-600 mb-2">{registration.email}</p>
{registration.detaineeInfo && (
<div className="text-sm text-gray-500">
<p>Detainee: {registration.detaineeInfo.name}</p>
<p>Facility: {registration.detaineeInfo.facility}</p>
</div>
)}
<p className="text-xs text-gray-400 mt-2">
Submitted: {new Date(registration.createdAt).toLocaleDateString()}
</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => window.location.href = `/user/applications/${registration.id}`}
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
>
View Details
</button>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
};
export default ClientDashboard;