← back to Dw Boardroom Governance
frontend/src/components/DelegationForm.tsx
133 lines
import { useState } from 'react';
import { api } from '../api';
import { useApi } from '../hooks/useApi';
import type { DelegationContract } from '../types';
export default function DelegationForm() {
const { data: delegations, refresh } = useApi(() => api.getDelegations(), []);
const [owner, setOwner] = useState('');
const [delegate, setDelegate] = useState('');
const [autonomy, setAutonomy] = useState(2);
const [metric, setMetric] = useState('');
const [suggestion, setSuggestion] = useState<any>(null);
const [searchText, setSearchText] = useState('');
async function create() {
if (!owner || !delegate) return;
try {
await api.createDelegation({ owner, delegate, autonomyLevel: autonomy, metric: metric || undefined });
setOwner(''); setDelegate(''); setMetric('');
refresh();
} catch (err: any) {
alert(err.message);
}
}
async function suggest() {
if (!searchText) return;
try {
const result = await api.suggestDelegate(searchText);
setSuggestion(result);
if (result.suggestedDelegate) setDelegate(result.suggestedDelegate);
} catch (err: any) {
console.error('[DelegationForm] suggest failed:', err.message);
}
}
async function revoke(id: string) {
try {
await api.revokeDelegation(id);
refresh();
} catch (err: any) {
console.error('[DelegationForm] revoke failed:', err.message);
}
}
return (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
{/* Create Form */}
<div style={styles.card}>
<h3 style={styles.cardTitle}>Create Delegation</h3>
{/* Keyword routing preview */}
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', gap: 6 }}>
<input style={styles.input} placeholder="Describe the task for auto-routing..." value={searchText} onChange={e => setSearchText(e.target.value)} />
<button style={styles.suggestBtn} onClick={suggest}>Suggest</button>
</div>
{suggestion && (
<div style={{ marginTop: 8, padding: 10, background: '#12121f', borderRadius: 6, fontSize: 12, border: '1px solid #252540' }}>
<div>Suggested: <strong style={{ color: '#8b5cf6' }}>{suggestion.suggestedDelegate || 'None'}</strong></div>
<div style={{ color: '#7a7f96', fontSize: 11 }}>{suggestion.reasoning}</div>
{suggestion.hierarchyPath && <div style={{ color: '#7a7f96', fontSize: 11 }}>Path: {suggestion.hierarchyPath}</div>}
</div>
)}
</div>
<div style={styles.formGrid}>
<label style={styles.label}>
Owner
<input style={styles.input} value={owner} onChange={e => setOwner(e.target.value)} placeholder="e.g. ceo, cfo" />
</label>
<label style={styles.label}>
Delegate
<input style={styles.input} value={delegate} onChange={e => setDelegate(e.target.value)} placeholder="e.g. frank, max" />
</label>
<label style={styles.label}>
Autonomy Level: {autonomy}
<input type="range" min={1} max={5} value={autonomy} onChange={e => setAutonomy(Number(e.target.value))} style={{ width: '100%' }} />
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: '#7a7f96' }}>
<span>1 Supervised</span><span>5 Autonomous</span>
</div>
</label>
<label style={styles.label}>
Success Metric
<input style={styles.input} value={metric} onChange={e => setMetric(e.target.value)} placeholder="KPI or completion criteria" />
</label>
</div>
<button style={styles.createBtn} onClick={create}>Create Delegation Contract</button>
</div>
{/* Active Contracts */}
<div style={styles.card}>
<h3 style={styles.cardTitle}>Active Contracts</h3>
<div style={styles.list}>
{(!delegations || delegations.length === 0) && <div style={styles.empty}>No delegations.</div>}
{delegations?.map((d: DelegationContract) => (
<div key={d.id} style={styles.contract}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontWeight: 600, fontSize: 13 }}>{d.owner} → {d.delegate}</span>
<span style={{ ...styles.statusBadge, background: d.status === 'active' ? '#22c55e22' : '#7a7f9622', color: d.status === 'active' ? '#22c55e' : '#7a7f96' }}>{d.status}</span>
</div>
<div style={{ fontSize: 11, color: '#7a7f96', marginTop: 4 }}>
Autonomy: {'★'.repeat(d.autonomy_level)}{'☆'.repeat(5 - d.autonomy_level)}
{d.hierarchy_path && ` | ${d.hierarchy_path}`}
</div>
{d.metric && <div style={{ fontSize: 11, color: '#b0b3c0', marginTop: 2 }}>Metric: {d.metric}</div>}
{d.status === 'active' && (
<button style={styles.revokeBtn} onClick={() => revoke(d.id)}>Revoke</button>
)}
</div>
))}
</div>
</div>
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
cardTitle: { margin: '0 0 16px', fontSize: 15, fontWeight: 700 },
formGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 },
label: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 11, fontWeight: 600, color: '#7a7f96' },
input: { padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit', flex: 1 },
suggestBtn: { padding: '8px 14px', borderRadius: 6, border: 'none', background: '#6366f1', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' },
createBtn: { padding: '10px 20px', borderRadius: 8, border: 'none', background: 'linear-gradient(135deg, #8b5cf6, #6366f1)', color: '#fff', fontWeight: 700, fontSize: 13, cursor: 'pointer', width: '100%', fontFamily: 'inherit' },
list: { display: 'flex', flexDirection: 'column', gap: 8 },
empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 20 },
contract: { background: '#12121f', borderRadius: 8, padding: 12, border: '1px solid #252540' },
statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
revokeBtn: { marginTop: 8, padding: '4px 12px', borderRadius: 4, border: '1px solid #ef4444', background: '#ef444422', color: '#ef4444', fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' },
};