Files
zeekay 6b38df60a4 feat: MPC wallet dashboard - full-stack implementation
Complete MPC wallet management platform rivaling Fireblocks/Utila/Fordefi:

Backend (Go):
- REST API with 60+ endpoints on chi router (pkg/api/)
- PostgreSQL schema with 13 tables (pkg/db/)
- JWT + MFA auth, bcrypt passwords, API key management
- Policy engine (deny/approve/require_approval)
- Transaction signing flow with approval workflow
- Subscription scheduler for recurring payments
- Payment requests with public pay pages
- Safe + ERC-4337 smart wallet integration (pkg/smart/)
- Rate limiting, webhook delivery (HMAC-SHA256), audit logging
- 27 passing tests (auth, policy engine, middleware)

Frontend (Next.js 14):
- 27 routes with monochrome dark theme (Tailwind + Geist fonts)
- Full API client with JWT auto-refresh
- React Query hooks for all resources
- Pages: dashboard, vaults, wallets, transactions, policies,
  whitelist, team, settings, API keys, webhooks, audit,
  subscriptions, payments, smart wallets

Infrastructure:
- Dockerfiles for API (Go) and dashboard (Next.js standalone)
- compose.yml for local development (PostgreSQL + API + dashboard)
- K8s manifests for mpc.lux.network + cloud.lux.network
- GitHub Actions deploy workflow (build → push GHCR → kubectl apply)
- TLS via cert-manager, Traefik IngressRoute for both domains
2025-12-27 13:11:38 -08:00

96 lines
2.9 KiB
TypeScript

'use client'
import { useState } from 'react'
export interface VaultFormProps {
initialName?: string
initialDescription?: string
initialAppId?: string
onSubmit: (data: { name: string; description: string; appId: string }) => void
onCancel: () => void
submitting?: boolean
}
export function VaultForm({
initialName = '',
initialDescription = '',
initialAppId = '',
onSubmit,
onCancel,
submitting = false,
}: VaultFormProps) {
const [name, setName] = useState(initialName)
const [description, setDescription] = useState(initialDescription)
const [appId, setAppId] = useState(initialAppId)
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
onSubmit({ name, description, appId })
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="vault-name">
Name
</label>
<input
id="vault-name"
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Vault name"
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="vault-desc">
Description
</label>
<textarea
id="vault-desc"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Optional description"
rows={3}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="vault-app-id">
App ID
</label>
<input
id="vault-app-id"
type="text"
value={appId}
onChange={(e) => setAppId(e.target.value)}
placeholder="Application identifier"
className="w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={onCancel}
disabled={submitting}
className="flex-1 rounded-md border border-border px-4 py-2 text-sm font-medium hover:bg-accent disabled:opacity-50"
>
Cancel
</button>
<button
type="submit"
disabled={submitting || !name}
className="flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
{submitting ? 'Saving...' : 'Save'}
</button>
</div>
</form>
)
}