aibanking.dev / views /DCRManagement.tsx
admin08077's picture
Upload 26 files
b8b3edf verified
import React, { useState, useEffect, useRef } from 'react';
import {
Key,
Shield,
Plus,
RefreshCw,
Trash2,
ChevronRight,
Fingerprint,
Terminal,
ExternalLink,
Eye,
EyeOff,
CheckCircle2,
AlertCircle,
Code
} from 'lucide-react';
import { ClientRegisterRequest, ClientRegisterResponse } from '../types/index.ts';
const MOCK_INITIAL_CLIENTS: ClientRegisterResponse[] = [
{
client_id: 'CLI-A192J-B002',
client_secret: 'SEC-K992-XXXX-Z102',
appId: 'LUMINA_CORE_NODE',
client_name: 'Lumina Primary Ledger',
clientDisplayName: 'Quantum Master Node',
redirect_uris: ['https://lumina.app/auth/callback'],
scope: ['accounts_details_transactions', 'customers_profiles'],
description: 'The main corporate ledger application for treasury management.',
token_endpoint_auth_method: 'client_secret_post'
}
];
const SCOPES = [
'accounts_details_transactions',
'accounts_routing_number',
'customers_profiles',
'accounts_statements',
'accounts_tax_statements'
];
const DCRManagement: React.FC = () => {
const [clients, setClients] = useState<ClientRegisterResponse[]>(MOCK_INITIAL_CLIENTS);
const [isRegistering, setIsRegistering] = useState(false);
const [selectedClient, setSelectedClient] = useState<ClientRegisterResponse | null>(null);
const [showSecret, setShowSecret] = useState<string | null>(null);
const [logs, setLogs] = useState<string[]>(['System: DCR Interface Initialized. Waiting for input...']);
const logEndRef = useRef<HTMLDivElement>(null);
const [form, setForm] = useState<ClientRegisterRequest>({
client_name: '',
redirect_uris: [''],
scope: [],
description: '',
appId: ''
});
useEffect(() => {
logEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [logs]);
const addLog = (msg: string) => setLogs(prev => [...prev, `${new Date().toLocaleTimeString()} - ${msg}`]);
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
addLog(`POST /api/dcr/v1/register - payload: ${JSON.stringify(form)}`);
setTimeout(() => {
const newClient: ClientRegisterResponse = {
...form,
client_id: `CLI-${Math.random().toString(36).substring(2, 9).toUpperCase()}`,
client_secret: `SEC-${Math.random().toString(36).substring(2, 12).toUpperCase()}`,
grant_types: ['authorization_code', 'refresh_token'],
token_endpoint_auth_method: 'client_secret_post'
};
setClients([...clients, newClient]);
setIsRegistering(false);
addLog(`HTTP/1.1 201 Created - New Client ID: ${newClient.client_id}`);
}, 1200);
};
const handleUpdate = (clientId: string) => {
addLog(`PUT /api/dcr/v1/register/${clientId} - Requesting metadata refresh...`);
setTimeout(() => {
addLog(`HTTP/1.1 200 OK - Metadata synchronized for ${clientId}`);
}, 800);
};
const handleRevoke = (clientId: string) => {
if (!confirm(`Confirm revocation of ${clientId}? This cannot be undone.`)) return;
addLog(`DELETE /api/dcr/v1/register/${clientId} - Initiating revocation...`);
setTimeout(() => {
setClients(clients.filter(c => c.client_id !== clientId));
if (selectedClient?.client_id === clientId) setSelectedClient(null);
addLog(`HTTP/1.1 204 No Content - Registration ${clientId} purged from vault.`);
}, 1000);
};
return (
<div className="max-w-7xl mx-auto space-y-8 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-bold text-white mb-2 flex items-center gap-3">
<Key className="text-blue-500" />
Dynamic Client Registry
</h2>
<p className="text-zinc-500 font-medium">Manage 4th party Data Recipients via FDX-standard DCR flows.</p>
</div>
<button
onClick={() => {
setIsRegistering(true);
setSelectedClient(null);
setForm({
client_name: '',
redirect_uris: [''],
scope: [],
description: '',
appId: ''
});
}}
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-xs uppercase tracking-widest transition-all shadow-lg shadow-blue-900/30"
>
<Plus size={18} />
<span>New Registration</span>
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-1 space-y-4 h-[calc(100vh-250px)] overflow-y-auto pr-2 custom-scrollbar">
{clients.map(client => (
<button
key={client.client_id}
onClick={() => {
setSelectedClient(client);
setIsRegistering(false);
}}
className={`w-full text-left p-6 rounded-3xl border transition-all group ${
selectedClient?.client_id === client.client_id
? 'bg-blue-600/10 border-blue-500 shadow-xl shadow-blue-900/10'
: 'bg-zinc-900 border-zinc-800 hover:border-zinc-700'
}`}
>
<div className="flex justify-between items-start mb-4">
<div className={`p-2 rounded-xl ${selectedClient?.client_id === client.client_id ? 'bg-blue-500 text-white' : 'bg-zinc-800 text-zinc-400 group-hover:text-blue-400'}`}>
<Fingerprint size={20} />
</div>
<div className="flex space-x-2">
<span className="text-[10px] font-black uppercase text-emerald-500 bg-emerald-500/10 px-2 py-0.5 rounded">Active</span>
</div>
</div>
<h4 className="text-white font-bold mb-1 group-hover:text-blue-400 transition-colors">{client.client_name}</h4>
<p className="text-zinc-500 text-xs mono tracking-tight mb-4">{client.client_id}</p>
<div className="flex items-center gap-4 text-zinc-600">
<div className="flex items-center gap-1.5">
<Shield size={12} />
<span className="text-[10px] font-bold uppercase">{client.scope.length} Scopes</span>
</div>
<div className="flex items-center gap-1.5">
<ExternalLink size={12} />
<span className="text-[10px] font-bold uppercase">1 URI</span>
</div>
</div>
</button>
))}
</div>
<div className="lg:col-span-2 space-y-8">
{isRegistering ? (
<div className="bg-zinc-900 border border-zinc-800 rounded-[40px] p-10 animate-in slide-in-from-right-4 duration-500">
<h3 className="text-2xl font-black text-white italic tracking-tighter uppercase mb-8 flex items-center gap-3">
<Plus className="text-blue-500" />
Register New Recipient
</h3>
<form onSubmit={handleRegister} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">App ID (optional)</label>
<input
value={form.appId}
className="w-full bg-black border border-zinc-800 rounded-2xl px-5 py-3.5 text-white outline-none focus:border-blue-500/50 transition-all"
placeholder="e.g. CORE_NODE_01"
onChange={e => setForm({...form, appId: e.target.value})}
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">Client Name</label>
<input
required
value={form.client_name}
className="w-full bg-black border border-zinc-800 rounded-2xl px-5 py-3.5 text-white outline-none focus:border-blue-500/50 transition-all"
placeholder="Legal Recipient Name"
onChange={e => setForm({...form, client_name: e.target.value})}
/>
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">Description</label>
<textarea
value={form.description}
className="w-full bg-black border border-zinc-800 rounded-2xl px-5 py-3.5 text-white outline-none focus:border-blue-500/50 transition-all h-24 resize-none"
placeholder="Brief overview of the recipient's purpose..."
onChange={e => setForm({...form, description: e.target.value})}
/>
</div>
<div className="pt-4 flex gap-4">
<button type="submit" className="flex-1 py-4 bg-blue-600 text-white rounded-2xl font-black text-xs uppercase tracking-widest transition-all">Execute Registration</button>
<button type="button" onClick={() => setIsRegistering(false)} className="px-8 py-4 bg-zinc-800 text-white rounded-2xl font-black text-xs uppercase tracking-widest transition-all">Cancel</button>
</div>
</form>
</div>
) : selectedClient ? (
<div className="bg-zinc-900 border border-zinc-800 rounded-[40px] p-10 animate-in zoom-in-95 duration-300">
<div className="flex justify-between items-start mb-10 pb-10 border-b border-zinc-800">
<div className="flex items-center gap-6">
<div className="w-16 h-16 bg-blue-600 rounded-3xl flex items-center justify-center">
<Shield size={32} className="text-white" />
</div>
<div>
<h3 className="text-3xl font-black text-white italic tracking-tighter uppercase">{selectedClient.client_name}</h3>
<p className="text-zinc-500 text-xs font-bold uppercase tracking-widest">Metadata Identity Record</p>
</div>
</div>
<button onClick={() => handleRevoke(selectedClient.client_id)} className="p-3 text-zinc-500 hover:text-rose-500 transition-colors bg-zinc-800 rounded-xl">
<Trash2 size={18} />
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
<div>
<p className="text-[10px] uppercase font-black text-zinc-600 tracking-widest mb-2">OAuth 2.0 Client Identifier</p>
<div className="bg-black border border-zinc-800 p-4 rounded-2xl flex items-center justify-between group">
<span className="text-white font-mono text-sm">{selectedClient.client_id}</span>
<Code size={16} className="text-zinc-700 group-hover:text-blue-500 transition-colors" />
</div>
</div>
<div>
<p className="text-[10px] uppercase font-black text-zinc-600 tracking-widest mb-2">Sync Status</p>
<div className="flex items-center gap-3">
<div className="w-2 h-2 rounded-full bg-emerald-500 shadow-[0_0_8px_#10b981]"></div>
<span className="text-white font-black text-xs uppercase">Handshake Parity</span>
</div>
</div>
</div>
</div>
) : (
<div className="h-full flex flex-col items-center justify-center bg-zinc-950/50 border-2 border-dashed border-zinc-900 rounded-[40px] text-center p-20">
<div className="w-24 h-24 bg-zinc-900 rounded-full flex items-center justify-center mb-6">
<Fingerprint size={48} className="text-zinc-800" />
</div>
<h3 className="text-xl font-bold text-zinc-600 uppercase tracking-[0.2em] mb-2">Registry Offline</h3>
<p className="text-zinc-700 text-sm max-w-xs font-medium">Select a recipient node or provision a new registration to view metadata.</p>
</div>
)}
<div className="bg-black border border-zinc-800 rounded-[32px] overflow-hidden shadow-2xl h-64 flex flex-col">
<div className="bg-zinc-900/80 px-6 py-3 border-b border-zinc-800 flex justify-between items-center">
<div className="flex items-center gap-2">
<Terminal size={14} className="text-blue-400" />
<span className="text-[10px] font-black uppercase tracking-widest text-zinc-500">DCR Node Trace Log</span>
</div>
</div>
<div className="flex-1 p-6 font-mono text-[11px] overflow-y-auto custom-scrollbar space-y-1">
{logs.map((log, i) => (
<div key={i} className="text-zinc-600">{log}</div>
))}
<div ref={logEndRef} />
</div>
</div>
</div>
</div>
</div>
);
};
export default DCRManagement;