![]() 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/components/payments/ |
'use client';
import React, { useState, useEffect } from 'react';
import { toast } from 'react-hot-toast';
import {
Shield,
Clock,
CheckCircle,
AlertCircle,
DollarSign,
Calendar,
Lock,
Unlock,
Eye,
Download
} from 'lucide-react';
interface EscrowAccount {
id: string;
caseId: string;
caseTitle: string;
amount: number;
currency: string;
status: string;
createdAt: string;
autoReleaseDate: string;
releasedAt?: string;
releasedBy?: string;
releaseReason?: string;
milestones: EscrowMilestone[];
}
interface EscrowMilestone {
id: string;
title: string;
description: string;
amount: number;
status: string;
dueDate: string;
completedAt?: string;
}
interface EscrowManagementProps {
userId: string;
userRole: string;
}
const EscrowManagement: React.FC<EscrowManagementProps> = ({ userId, userRole }) => {
const [escrowAccounts, setEscrowAccounts] = useState<EscrowAccount[]>([]);
const [loading, setLoading] = useState(true);
const [selectedAccount, setSelectedAccount] = useState<EscrowAccount | null>(null);
const [showDetailsModal, setShowDetailsModal] = useState(false);
const [showReleaseModal, setShowReleaseModal] = useState(false);
const [releaseReason, setReleaseReason] = useState('');
const [filterStatus, setFilterStatus] = useState('all');
useEffect(() => {
fetchEscrowAccounts();
}, [userId]);
const fetchEscrowAccounts = async () => {
try {
setLoading(true);
const response = await fetch('/api/user/escrow-accounts');
if (response.ok) {
const data = await response.json();
setEscrowAccounts(data.escrowAccounts || []);
} else {
console.error('Failed to fetch escrow accounts');
}
} catch (error) {
console.error('Error fetching escrow accounts:', error);
toast.error('Failed to load escrow accounts');
} finally {
setLoading(false);
}
};
const handleReleaseEscrow = async (accountId: string) => {
if (!releaseReason.trim()) {
toast.error('Please provide a reason for the release');
return;
}
try {
const response = await fetch(`/api/user/escrow-accounts/${accountId}/release`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
reason: releaseReason
})
});
if (response.ok) {
toast.success('Escrow funds released successfully');
setShowReleaseModal(false);
setReleaseReason('');
fetchEscrowAccounts(); // Refresh data
} else {
const error = await response.json();
toast.error(error.message || 'Failed to release escrow funds');
}
} catch (error) {
console.error('Error releasing escrow:', error);
toast.error('Failed to release escrow funds');
}
};
const getStatusIcon = (status: string) => {
switch (status.toLowerCase()) {
case 'active':
return <Lock className="h-4 w-4 text-blue-500" />;
case 'released':
return <Unlock className="h-4 w-4 text-green-500" />;
case 'pending':
return <Clock className="h-4 w-4 text-yellow-500" />;
case 'disputed':
return <AlertCircle className="h-4 w-4 text-red-500" />;
default:
return <Shield className="h-4 w-4 text-gray-500" />;
}
};
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'active':
return 'text-blue-600 bg-blue-50';
case 'released':
return 'text-green-600 bg-green-50';
case 'pending':
return 'text-yellow-600 bg-yellow-50';
case 'disputed':
return 'text-red-600 bg-red-50';
default:
return 'text-gray-600 bg-gray-50';
}
};
const formatCurrency = (amount: number, currency: string = 'CAD') => {
return new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: currency
}).format(amount);
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-CA', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
};
const getDaysUntilRelease = (releaseDate: string) => {
const now = new Date();
const release = new Date(releaseDate);
const diffTime = release.getTime() - now.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
};
const filteredAccounts = escrowAccounts.filter(account => {
if (filterStatus === 'all') return true;
return account.status.toLowerCase() === filterStatus;
});
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>
<span className="ml-3 text-gray-600">Loading escrow accounts...</span>
</div>
);
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex justify-between items-center">
<div>
<h2 className="text-2xl font-bold text-gray-900">Escrow Management</h2>
<p className="text-gray-600">Manage escrow accounts and fund releases</p>
</div>
<div className="flex space-x-2">
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md text-sm"
>
<option value="all">All Status</option>
<option value="active">Active</option>
<option value="pending">Pending</option>
<option value="released">Released</option>
<option value="disputed">Disputed</option>
</select>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center">
<div className="p-2 bg-blue-100 rounded-lg">
<Shield className="h-6 w-6 text-blue-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Total Escrow</p>
<p className="text-2xl font-bold text-gray-900">
{formatCurrency(
escrowAccounts.reduce((sum, account) => sum + account.amount, 0)
)}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center">
<div className="p-2 bg-green-100 rounded-lg">
<Lock className="h-6 w-6 text-green-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Active Accounts</p>
<p className="text-2xl font-bold text-gray-900">
{escrowAccounts.filter(a => a.status === 'ACTIVE').length}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow 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 Release</p>
<p className="text-2xl font-bold text-gray-900">
{escrowAccounts.filter(a => a.status === 'PENDING').length}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center">
<div className="p-2 bg-purple-100 rounded-lg">
<Unlock className="h-6 w-6 text-purple-600" />
</div>
<div className="ml-4">
<p className="text-sm font-medium text-gray-600">Released</p>
<p className="text-2xl font-bold text-gray-900">
{escrowAccounts.filter(a => a.status === 'RELEASED').length}
</p>
</div>
</div>
</div>
</div>
{/* Escrow Accounts Table */}
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900">Escrow Accounts</h3>
</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">
Case
</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">
Auto Release
</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">
{filteredAccounts.map((account) => {
const daysUntilRelease = getDaysUntilRelease(account.autoReleaseDate);
return (
<tr key={account.id}>
<td className="px-6 py-4 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
{account.caseTitle}
</div>
<div className="text-sm text-gray-500">
ID: {account.caseId}
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">
{formatCurrency(account.amount, account.currency)}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(account.status)}`}>
{getStatusIcon(account.status)}
<span className="ml-1">{account.status}</span>
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">
{formatDate(account.autoReleaseDate)}
</div>
{account.status === 'ACTIVE' && daysUntilRelease > 0 && (
<div className="text-xs text-gray-500">
{daysUntilRelease} days remaining
</div>
)}
{account.status === 'ACTIVE' && daysUntilRelease <= 0 && (
<div className="text-xs text-red-500">
Overdue for release
</div>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
onClick={() => {
setSelectedAccount(account);
setShowDetailsModal(true);
}}
className="text-blue-600 hover:text-blue-900 mr-3"
>
<Eye className="h-4 w-4" />
</button>
{account.status === 'ACTIVE' && userRole === 'LAWYER' && (
<button
onClick={() => {
setSelectedAccount(account);
setShowReleaseModal(true);
}}
className="text-green-600 hover:text-green-900"
>
<Unlock className="h-4 w-4" />
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{filteredAccounts.length === 0 && (
<div className="text-center py-12">
<Shield className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">No escrow accounts</h3>
<p className="mt-1 text-sm text-gray-500">
Escrow accounts will appear here when payments are made.
</p>
</div>
)}
</div>
{/* Account Details Modal */}
{showDetailsModal && selectedAccount && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div className="mt-3">
<h3 className="text-lg font-medium text-gray-900 mb-4">Escrow Account Details</h3>
<div className="space-y-3">
<div>
<label className="text-sm font-medium text-gray-500">Case</label>
<p className="text-sm text-gray-900">{selectedAccount.caseTitle}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Amount</label>
<p className="text-sm text-gray-900">
{formatCurrency(selectedAccount.amount, selectedAccount.currency)}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Status</label>
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(selectedAccount.status)}`}>
{getStatusIcon(selectedAccount.status)}
<span className="ml-1">{selectedAccount.status}</span>
</span>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Created</label>
<p className="text-sm text-gray-900">{formatDate(selectedAccount.createdAt)}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Auto Release Date</label>
<p className="text-sm text-gray-900">{formatDate(selectedAccount.autoReleaseDate)}</p>
</div>
{selectedAccount.releasedAt && (
<>
<div>
<label className="text-sm font-medium text-gray-500">Released At</label>
<p className="text-sm text-gray-900">{formatDate(selectedAccount.releasedAt)}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Release Reason</label>
<p className="text-sm text-gray-900">{selectedAccount.releaseReason}</p>
</div>
</>
)}
</div>
<div className="mt-6 flex justify-end">
<button
onClick={() => setShowDetailsModal(false)}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
>
Close
</button>
</div>
</div>
</div>
</div>
)}
{/* Release Modal */}
{showReleaseModal && selectedAccount && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div className="mt-3">
<h3 className="text-lg font-medium text-gray-900 mb-4">Release Escrow Funds</h3>
<div className="space-y-4">
<div>
<label className="text-sm font-medium text-gray-500">Case</label>
<p className="text-sm text-gray-900">{selectedAccount.caseTitle}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">Amount</label>
<p className="text-sm text-gray-900">
{formatCurrency(selectedAccount.amount, selectedAccount.currency)}
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Release Reason *
</label>
<textarea
value={releaseReason}
onChange={(e) => setReleaseReason(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
rows={3}
placeholder="Explain why you're releasing these funds..."
/>
</div>
</div>
<div className="mt-6 flex justify-end space-x-3">
<button
onClick={() => {
setShowReleaseModal(false);
setReleaseReason('');
}}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
>
Cancel
</button>
<button
onClick={() => handleReleaseEscrow(selectedAccount.id)}
className="px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-md hover:bg-green-700"
>
Release Funds
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default EscrowManagement;