aibanking.dev / views /Counterparties.tsx
admin08077's picture
Upload 26 files
b8b3edf verified
import React, { useState } from 'react';
/* Fix: react-router-dom exports may be flaky in this environment, using standard v6 imports */
import { useNavigate } from 'react-router-dom';
import {
Users,
Search,
Plus,
Mail,
Globe,
ShieldCheck,
MoreHorizontal,
Loader2,
X,
ArrowRight,
Building2,
Calendar,
CreditCard,
ExternalLink
} from 'lucide-react';
import { Counterparty } from '../types/index';
const MOCK_CP: Counterparty[] = [
{ id: 'CP-8801', name: 'Global Logistics Corp', email: 'billing@globallogistics.io', status: 'ACTIVE', createdAt: '2023-11-20', accounts: [{ id: 'ACC-1', accountType: 'CHECKING', accountNumber: '•••• 4421' }] },
{ id: 'CP-9902', name: 'Neural Dynamics Research', email: 'treasury@neuraldynamics.ai', status: 'PENDING', createdAt: '2024-01-05', accounts: [] },
{ id: 'CP-1105', name: 'Skyline Real Estate', email: 'payments@skyline.co', status: 'ACTIVE', createdAt: '2023-08-12', accounts: [{ id: 'ACC-2', accountType: 'SAVINGS', accountNumber: '•••• 1022' }] },
];
const Counterparties: React.FC = () => {
const navigate = useNavigate();
const [partners, setPartners] = useState(MOCK_CP);
const [showModal, setShowModal] = useState(false);
const [showProfile, setShowProfile] = useState(false);
const [selectedPartner, setSelectedPartner] = useState<Counterparty | null>(null);
const [connecting, setConnecting] = useState(false);
const [newPartner, setNewPartner] = useState({ name: '', email: '' });
const registerPartner = () => {
if (!newPartner.name || !newPartner.email) return;
setConnecting(true);
setTimeout(() => {
const partner: Counterparty = {
id: `CP-${Math.floor(1000 + Math.random() * 9000)}`,
name: newPartner.name,
email: newPartner.email,
status: 'PENDING',
createdAt: new Date().toISOString().split('T')[0],
accounts: []
};
setPartners([...partners, partner]);
setConnecting(false);
setShowModal(false);
setNewPartner({ name: '', email: '' });
}, 1500);
};
const openProfile = (partner: Counterparty) => {
setSelectedPartner(partner);
setShowProfile(true);
};
const handleSendFunds = (partner: Counterparty) => {
// Navigate to disbursements with partner intent
navigate('/payments', { state: { selectedPayeeId: partner.id } });
};
return (
<div className="space-y-10 animate-in fade-in duration-700">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6">
<div>
<h2 className="text-3xl font-black text-white italic tracking-tighter uppercase mb-2">Partner <span className="text-blue-500 not-italic">CRM</span></h2>
<p className="text-zinc-500 text-[10px] font-black uppercase tracking-[0.3em]">Managed Third-Party Entities & KYC Handshakes</p>
</div>
<button
onClick={() => setShowModal(true)}
className="flex items-center space-x-2 px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-2xl font-black text-[10px] uppercase tracking-widest transition-all shadow-xl shadow-blue-900/30"
>
<Plus size={14} />
<span>Register Counterparty</span>
</button>
</div>
<div className="bg-zinc-950 border border-zinc-900 rounded-[3rem] overflow-hidden shadow-2xl">
<div className="p-10 border-b border-zinc-900 flex flex-col md:flex-row justify-between items-center bg-black/20 gap-6">
<div className="flex items-center space-x-4">
<div className="p-3 bg-blue-500/10 text-blue-500 rounded-2xl">
<Users size={20} />
</div>
<h3 className="text-white font-black uppercase tracking-[0.2em] italic">Active Directory</h3>
</div>
<div className="relative w-full md:w-80">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-600" size={14} />
<input
placeholder="Search Partners..."
className="w-full bg-black border border-zinc-800 rounded-xl py-3 pl-10 pr-4 text-[10px] font-black uppercase tracking-widest text-white outline-none focus:border-blue-500/50 transition-all"
/>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-px bg-zinc-900/50">
{partners.map(cp => (
<div key={cp.id} className="bg-zinc-950 p-10 hover:bg-white/[0.02] transition-all group border border-transparent hover:border-zinc-800 relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-0 group-hover:opacity-5 transition-opacity">
<Building2 size={120} />
</div>
<div className="relative z-10">
<div className="flex justify-between items-start mb-10">
<div className="w-16 h-16 rounded-2xl bg-zinc-900 border border-zinc-800 flex items-center justify-center text-zinc-600 group-hover:text-blue-400 transition-colors shadow-2xl">
<Globe size={32} />
</div>
<div className={`px-3 py-1 rounded-full text-[8px] font-black uppercase tracking-widest ${cp.status === 'ACTIVE' ? 'bg-emerald-500/10 text-emerald-500' : 'bg-amber-500/10 text-amber-500'}`}>
{cp.status}
</div>
</div>
<h4 className="text-xl font-black text-white uppercase italic tracking-tighter mb-1">{cp.name}</h4>
<p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mb-10 truncate">{cp.email}</p>
<div className="flex gap-4">
<button onClick={() => openProfile(cp)} className="flex-1 py-4 bg-zinc-900 text-white rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all border border-zinc-800 hover:bg-zinc-800 flex items-center justify-center gap-2">
Profile
</button>
<button onClick={() => handleSendFunds(cp)} className="flex-1 py-4 bg-blue-600 text-white rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all hover:bg-blue-500 flex items-center justify-center gap-2">
Send Funds
</button>
</div>
</div>
</div>
))}
</div>
</div>
{/* Profile Modal */}
{showProfile && selectedPartner && (
<div className="fixed inset-0 z-[110] flex items-center justify-center p-6 backdrop-blur-md bg-black/70">
<div className="bg-zinc-950 border border-zinc-900 w-full max-w-2xl rounded-[3rem] p-10 shadow-2xl animate-in zoom-in-95 duration-300">
<div className="flex justify-between items-start mb-10 pb-8 border-b border-zinc-900">
<div className="flex items-center gap-6">
<div className="w-16 h-16 bg-blue-600/10 text-blue-500 rounded-3xl flex items-center justify-center">
<Building2 size={32} />
</div>
<div>
<h3 className="text-3xl font-black text-white italic tracking-tighter uppercase">{selectedPartner.name}</h3>
<p className="text-[10px] text-zinc-500 font-black uppercase tracking-widest mt-1">Institutional Entity Record • {selectedPartner.id}</p>
</div>
</div>
<button onClick={() => setShowProfile(false)} className="p-2 text-zinc-500 hover:text-white transition-colors"><X size={24} /></button>
</div>
<div className="space-y-8">
<div className="grid grid-cols-2 gap-8">
<div className="space-y-6">
<div>
<p className="text-[9px] font-black text-zinc-600 uppercase tracking-widest mb-2">Entity Email</p>
<p className="text-white font-bold flex items-center gap-2">
<Mail size={14} className="text-blue-500" />
{selectedPartner.email}
</p>
</div>
<div>
<p className="text-[9px] font-black text-zinc-600 uppercase tracking-widest mb-2">Relationship Established</p>
<p className="text-white font-bold flex items-center gap-2">
<Calendar size={14} className="text-blue-500" />
{new Date(selectedPartner.createdAt).toLocaleDateString()}
</p>
</div>
</div>
<div className="space-y-6">
<div>
<p className="text-[9px] font-black text-zinc-600 uppercase tracking-widest mb-2">KYC Verification Status</p>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${selectedPartner.status === 'ACTIVE' ? 'bg-emerald-500 animate-pulse' : 'bg-amber-500'}`}></div>
<span className={`${selectedPartner.status === 'ACTIVE' ? 'text-emerald-500' : 'text-amber-500'} font-black text-[10px] uppercase tracking-widest`}>
{selectedPartner.status === 'ACTIVE' ? 'Identity Verified' : 'Handshake Pending'}
</span>
</div>
</div>
<div>
<p className="text-[9px] font-black text-zinc-600 uppercase tracking-widest mb-2">Primary Registry Type</p>
<p className="text-white font-black italic uppercase tracking-tighter">FDX_CORPORATE_NODE</p>
</div>
</div>
</div>
<div className="p-6 bg-black border border-zinc-800 rounded-3xl space-y-4">
<p className="text-[9px] font-black text-zinc-500 uppercase tracking-widest flex items-center gap-2">
<CreditCard size={12} />
Linked Financial Handshakes
</p>
{selectedPartner.accounts && selectedPartner.accounts.length > 0 ? (
selectedPartner.accounts.map(acc => (
<div key={acc.id} className="flex justify-between items-center bg-zinc-900/50 p-4 rounded-xl border border-zinc-800">
<div>
<p className="text-white text-xs font-bold uppercase">{acc.accountType} Account</p>
<p className="text-zinc-500 text-[10px] font-mono mt-0.5">{acc.accountNumber}</p>
</div>
<span className="text-[8px] font-black uppercase text-emerald-500 border border-emerald-500/20 px-2 py-0.5 rounded">RTP Enabled</span>
</div>
))
) : (
<div className="bg-zinc-900/50 p-4 rounded-xl border border-dashed border-zinc-800 text-center">
<p className="text-zinc-600 text-[9px] font-black uppercase tracking-widest">No verified external accounts found</p>
</div>
)}
</div>
<div className="flex gap-4">
<button className="flex-1 py-5 bg-zinc-900 hover:bg-zinc-800 text-white rounded-[2rem] font-black text-xs uppercase tracking-widest transition-all border border-zinc-800 flex items-center justify-center gap-3">
<ExternalLink size={18} />
Open Documents
</button>
<button
onClick={() => handleSendFunds(selectedPartner)}
className="flex-1 py-5 bg-blue-600 hover:bg-blue-500 text-white rounded-[2rem] font-black text-xs uppercase tracking-widest transition-all flex items-center justify-center gap-3 shadow-xl"
>
Send Disbursement
</button>
</div>
</div>
</div>
</div>
)}
{/* Registration Modal */}
{showModal && (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-6 backdrop-blur-sm bg-black/60">
<div className="bg-zinc-950 border border-zinc-900 w-full max-w-lg rounded-[3rem] p-10 shadow-2xl animate-in zoom-in-95">
<div className="flex justify-between items-start mb-10">
<div className="flex items-center gap-4">
<div className="w-14 h-14 bg-blue-600/10 text-blue-500 rounded-2xl flex items-center justify-center">
<ShieldCheck size={28} />
</div>
<div>
<h3 className="text-2xl font-black text-white italic tracking-tighter uppercase">Add <span className="text-blue-500 not-italic">Partner</span></h3>
<p className="text-[10px] text-zinc-500 font-black uppercase tracking-widest">KYC/KYB Initialization</p>
</div>
</div>
<button onClick={() => setShowModal(false)} className="p-2 text-zinc-600 hover:text-white"><X size={24} /></button>
</div>
<div className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">Entity Name</label>
<input
value={newPartner.name}
onChange={(e) => setNewPartner({...newPartner, name: e.target.value})}
placeholder="Legal Company Name"
className="w-full bg-black border border-zinc-800 focus:border-blue-500/50 rounded-xl py-4 px-4 text-white font-bold outline-none transition-all"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">Treasury Email</label>
<input
value={newPartner.email}
onChange={(e) => setNewPartner({...newPartner, email: e.target.value})}
placeholder="treasury@partner.io"
className="w-full bg-black border border-zinc-800 focus:border-blue-500/50 rounded-xl py-4 px-4 text-white font-bold outline-none transition-all"
/>
</div>
<button
onClick={registerPartner}
disabled={connecting || !newPartner.name}
className="w-full mt-4 py-5 bg-blue-600 hover:bg-blue-500 text-white rounded-[2rem] font-black text-xs uppercase tracking-[0.3em] transition-all flex items-center justify-center gap-4 shadow-xl shadow-blue-900/30"
>
{connecting ? <Loader2 className="animate-spin" size={20} /> : <ArrowRight size={20} />}
<span>{connecting ? 'Dispatching Handshake...' : 'Register Entity'}</span>
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default Counterparties;