mirror of
https://github.com/luxfi/mpc.git
synced 2026-11-03 09:30:25 +00:00
Measured on the rendered pixels, not the source: the MPC dashboard's home page
carried 1.05% of every frame in hue at mean chroma 1.279, against explore's
0.004 next door. It came from four places, all literals no hostname could
retheme:
app/page.tsx violet→blue gradient wordmark (from-violet-500 to-blue-500)
feature headings text-violet-400 / text-blue-400 /
text-emerald-400, and ten chain chips on border-violet-500/20
bg-violet-500/10 text-violet-300 — the #ece7f7 wash that was
10,776 pixels of one screenshot
app/docs/page.tsx the same violet, plus border-violet/blue/emerald/purple-500/30
and 15 more files reaching for Tailwind's chromatic ramps directly. Those now
resolve through the tokens @luxfi/ui owns: green/emerald → --color-status-good,
red/rose → --color-status-bad, yellow/amber → --color-status-warn (achromatic —
attention is the loudest neutral in Lux, not a hue), everything else onto
foreground / muted / border.
After: warm 0%, tinted-neutral 0%, mean chroma 0.000 at 1440x900 and 390x844,
in light and dark. Distinction is carried by the LABEL, which already read
CGGMP21 / FROST / Bridge.
@luxfi/ui 7.4.5 -> 7.4.9.
Co-authored-by: Hanzo Dev <[email protected]>
319 lines
14 KiB
TypeScript
319 lines
14 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { useParams } from 'next/navigation'
|
|
import Link from 'next/link'
|
|
import { Nav } from '@/components/layout/nav'
|
|
import { StatusBadge } from '@/components/common/status-badge'
|
|
import { api } from '@/lib/api'
|
|
import type { Intent } from '@/lib/types'
|
|
|
|
const intentSteps = ['pending_sign', 'signed', 'co_signed', 'recorded', 'matched', 'settling', 'settled', 'verified']
|
|
|
|
export default function IntentDetailPage() {
|
|
const { id } = useParams<{ id: string }>()
|
|
const [intent, setIntent] = useState<Intent | null>(null)
|
|
const [error, setError] = useState('')
|
|
const [acting, setActing] = useState(false)
|
|
const [signature, setSignature] = useState('')
|
|
const [keyId, setKeyId] = useState('')
|
|
|
|
useEffect(() => {
|
|
if (!id) return
|
|
api.getIntent(id).then(setIntent).catch((e) => setError(e.message))
|
|
}, [id])
|
|
|
|
async function handleSign() {
|
|
if (!signature.trim()) return
|
|
setActing(true)
|
|
setError('')
|
|
try {
|
|
const updated = await api.signIntent(id, { signature })
|
|
setIntent(updated)
|
|
setSignature('')
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : 'Signing failed')
|
|
} finally {
|
|
setActing(false)
|
|
}
|
|
}
|
|
|
|
async function handleCoSign() {
|
|
if (!keyId.trim()) return
|
|
setActing(true)
|
|
setError('')
|
|
try {
|
|
const updated = await api.coSignIntent(id, { key_id: keyId })
|
|
setIntent(updated)
|
|
setKeyId('')
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : 'Co-signing failed')
|
|
} finally {
|
|
setActing(false)
|
|
}
|
|
}
|
|
|
|
const currentStep = intent ? intentSteps.indexOf(intent.status) : -1
|
|
|
|
return (
|
|
<>
|
|
<Nav />
|
|
<main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
|
<div className="mb-8 flex items-center gap-2 text-sm text-muted-foreground">
|
|
<Link href="/intents" className="hover:text-foreground">Intents</Link>
|
|
<span>/</span>
|
|
<span className="font-mono">{id?.slice(0, 8)}...</span>
|
|
</div>
|
|
|
|
{error && <p className="mb-4 text-sm text-destructive">{error}</p>}
|
|
{!intent && !error && <p className="text-sm text-muted-foreground">Loading...</p>}
|
|
|
|
{intent && (
|
|
<>
|
|
<div className="mb-8 flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight capitalize">{intent.intent_type} Intent</h1>
|
|
<p className="mt-1 font-mono text-xs text-muted-foreground">{intent.id}</p>
|
|
</div>
|
|
<StatusBadge status={intent.status} />
|
|
</div>
|
|
|
|
{/* Lifecycle Progress */}
|
|
<div className="mb-8 rounded-lg border border-border bg-card p-6">
|
|
<h2 className="mb-4 text-sm font-medium uppercase tracking-wider text-muted-foreground">Lifecycle</h2>
|
|
<div className="flex items-center gap-1 overflow-x-auto">
|
|
{intentSteps.map((step, i) => {
|
|
const isActive = i <= currentStep
|
|
const isCurrent = step === intent.status
|
|
return (
|
|
<div key={step} className="flex items-center gap-1">
|
|
{i > 0 && (
|
|
<div className={`h-0.5 w-6 ${isActive ? 'bg-[var(--color-status-good)]' : 'bg-border'}`} />
|
|
)}
|
|
<div
|
|
className={`whitespace-nowrap rounded-md px-2.5 py-1.5 text-xs font-medium capitalize ${
|
|
isCurrent
|
|
? 'bg-primary text-primary-foreground'
|
|
: isActive
|
|
? 'bg-[var(--color-status-good)]/10 text-[var(--color-status-good)]'
|
|
: 'bg-muted text-muted-foreground'
|
|
}`}
|
|
>
|
|
{step.replace(/_/g, ' ')}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Intent Details */}
|
|
<div className="mb-8 rounded-lg border border-border bg-card p-6">
|
|
<h2 className="mb-4 text-sm font-medium uppercase tracking-wider text-muted-foreground">Details</h2>
|
|
<dl className="grid grid-cols-2 gap-4 text-sm sm:grid-cols-3">
|
|
<div>
|
|
<dt className="text-muted-foreground">Type</dt>
|
|
<dd className="mt-1 capitalize">{intent.intent_type}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-muted-foreground">Chain</dt>
|
|
<dd className="mt-1 capitalize">{intent.chain}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-muted-foreground">Amount</dt>
|
|
<dd className="mt-1 font-mono">{intent.amount}</dd>
|
|
</div>
|
|
{intent.token && (
|
|
<div>
|
|
<dt className="text-muted-foreground">Token</dt>
|
|
<dd className="mt-1 font-mono text-xs">{intent.token}</dd>
|
|
</div>
|
|
)}
|
|
{intent.to_address && (
|
|
<div className="col-span-2">
|
|
<dt className="text-muted-foreground">To Address</dt>
|
|
<dd className="mt-1 font-mono text-xs break-all">{intent.to_address}</dd>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<dt className="text-muted-foreground">Wallet</dt>
|
|
<dd className="mt-1">
|
|
<Link href={`/wallets/${intent.wallet_id}`} className="font-mono text-xs hover:underline">
|
|
{intent.wallet_id.slice(0, 12)}...
|
|
</Link>
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-muted-foreground">Created</dt>
|
|
<dd className="mt-1">{new Date(intent.created_at).toLocaleString()}</dd>
|
|
</div>
|
|
{intent.expires_at && (
|
|
<div>
|
|
<dt className="text-muted-foreground">Expires</dt>
|
|
<dd className="mt-1">{new Date(intent.expires_at).toLocaleString()}</dd>
|
|
</div>
|
|
)}
|
|
</dl>
|
|
</div>
|
|
|
|
{/* Intent Hash */}
|
|
<div className="mb-8 rounded-lg border border-border bg-card p-6">
|
|
<h2 className="mb-4 text-sm font-medium uppercase tracking-wider text-muted-foreground">Intent Hash</h2>
|
|
<p className="font-mono text-xs break-all">{intent.intent_hash}</p>
|
|
</div>
|
|
|
|
{/* Signatures */}
|
|
{(intent.signature || intent.co_signature) && (
|
|
<div className="mb-8 rounded-lg border border-border bg-card p-6">
|
|
<h2 className="mb-4 text-sm font-medium uppercase tracking-wider text-muted-foreground">Signatures</h2>
|
|
<dl className="space-y-3 text-sm">
|
|
{intent.signature && (
|
|
<div>
|
|
<dt className="text-muted-foreground">User Signature</dt>
|
|
<dd className="mt-1 font-mono text-xs break-all">{intent.signature}</dd>
|
|
</div>
|
|
)}
|
|
{intent.co_signature && (
|
|
<div>
|
|
<dt className="text-muted-foreground">HSM Co-Signature</dt>
|
|
<dd className="mt-1 font-mono text-xs break-all">{intent.co_signature}</dd>
|
|
</div>
|
|
)}
|
|
{intent.co_signer_key_id && (
|
|
<div>
|
|
<dt className="text-muted-foreground">Co-Signer Key ID</dt>
|
|
<dd className="mt-1 font-mono text-xs">{intent.co_signer_key_id}</dd>
|
|
</div>
|
|
)}
|
|
</dl>
|
|
</div>
|
|
)}
|
|
|
|
{/* On-chain Recording */}
|
|
{(intent.on_chain_tx_hash || intent.recorded_at) && (
|
|
<div className="mb-8 rounded-lg border border-border bg-card p-6">
|
|
<h2 className="mb-4 text-sm font-medium uppercase tracking-wider text-muted-foreground">On-Chain Record</h2>
|
|
<dl className="grid grid-cols-2 gap-4 text-sm">
|
|
{intent.on_chain_tx_hash && (
|
|
<div className="col-span-2">
|
|
<dt className="text-muted-foreground">TX Hash</dt>
|
|
<dd className="mt-1 font-mono text-xs break-all">{intent.on_chain_tx_hash}</dd>
|
|
</div>
|
|
)}
|
|
{intent.recorded_at && (
|
|
<div>
|
|
<dt className="text-muted-foreground">Recorded At</dt>
|
|
<dd className="mt-1">{new Date(intent.recorded_at).toLocaleString()}</dd>
|
|
</div>
|
|
)}
|
|
{intent.recorded_block != null && (
|
|
<div>
|
|
<dt className="text-muted-foreground">Block</dt>
|
|
<dd className="mt-1 font-mono">{intent.recorded_block}</dd>
|
|
</div>
|
|
)}
|
|
</dl>
|
|
</div>
|
|
)}
|
|
|
|
{/* Matching */}
|
|
{(intent.match_id || intent.matched_at) && (
|
|
<div className="mb-8 rounded-lg border border-border bg-card p-6">
|
|
<h2 className="mb-4 text-sm font-medium uppercase tracking-wider text-muted-foreground">Match</h2>
|
|
<dl className="grid grid-cols-2 gap-4 text-sm">
|
|
{intent.match_id && (
|
|
<div>
|
|
<dt className="text-muted-foreground">Match ID</dt>
|
|
<dd className="mt-1 font-mono text-xs">{intent.match_id}</dd>
|
|
</div>
|
|
)}
|
|
{intent.matched_at && (
|
|
<div>
|
|
<dt className="text-muted-foreground">Matched At</dt>
|
|
<dd className="mt-1">{new Date(intent.matched_at).toLocaleString()}</dd>
|
|
</div>
|
|
)}
|
|
</dl>
|
|
</div>
|
|
)}
|
|
|
|
{/* Status History */}
|
|
{intent.status_history && intent.status_history.length > 0 && (
|
|
<div className="mb-8 rounded-lg border border-border bg-card p-6">
|
|
<h2 className="mb-4 text-sm font-medium uppercase tracking-wider text-muted-foreground">Status History</h2>
|
|
<div className="space-y-3">
|
|
{intent.status_history.map((t, i) => (
|
|
<div key={i} className="flex items-start gap-3 text-sm">
|
|
<div className="flex items-center gap-2 text-muted-foreground">
|
|
<StatusBadge status={t.from || 'new'} />
|
|
<span>-></span>
|
|
<StatusBadge status={t.to} />
|
|
</div>
|
|
<span className="text-xs text-muted-foreground">{new Date(t.timestamp).toLocaleString()}</span>
|
|
{t.detail && <span className="text-xs text-muted-foreground">- {t.detail}</span>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Actions */}
|
|
{intent.status === 'pending_sign' && (
|
|
<div className="rounded-lg border border-border bg-card p-6 space-y-4">
|
|
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">Sign Intent</h2>
|
|
<div className="flex items-end gap-3">
|
|
<div className="flex-1">
|
|
<label className="mb-1.5 block text-xs text-muted-foreground">Signature (hex)</label>
|
|
<input
|
|
type="text"
|
|
value={signature}
|
|
onChange={(e) => setSignature(e.target.value)}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-ring"
|
|
placeholder="0x..."
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={handleSign}
|
|
disabled={acting || !signature.trim()}
|
|
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
|
>
|
|
{acting ? 'Signing...' : 'Sign'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{intent.status === 'signed' && (
|
|
<div className="rounded-lg border border-border bg-card p-6 space-y-4">
|
|
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">HSM Co-Sign</h2>
|
|
<p className="text-xs text-muted-foreground">
|
|
The server will sign directly with the HSM. Provide the key ID to use.
|
|
</p>
|
|
<div className="flex items-end gap-3">
|
|
<div className="flex-1">
|
|
<label className="mb-1.5 block text-xs text-muted-foreground">HSM Key ID</label>
|
|
<input
|
|
type="text"
|
|
value={keyId}
|
|
onChange={(e) => setKeyId(e.target.value)}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-ring"
|
|
placeholder="aws-kms://key-id or gcp-kms://..."
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={handleCoSign}
|
|
disabled={acting || !keyId.trim()}
|
|
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
|
>
|
|
{acting ? 'Co-signing...' : 'Co-Sign with HSM'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</main>
|
|
</>
|
|
)
|
|
}
|