feat: NASA mission-control theme for all OMNL dashboards
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m41s
CI/CD Pipeline / Security Scanning (push) Successful in 2m32s
CI/CD Pipeline / Lint and Format (push) Failing after 51s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 30s
Validation / validate-genesis (push) Successful in 34s
Validation / validate-terraform (push) Failing after 36s
Validation / validate-kubernetes (push) Failing after 11s
Validation / validate-smart-contracts (push) Failing after 11s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 21s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m41s
CI/CD Pipeline / Security Scanning (push) Successful in 2m32s
CI/CD Pipeline / Lint and Format (push) Failing after 51s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 30s
Validation / validate-genesis (push) Successful in 34s
Validation / validate-terraform (push) Failing after 36s
Validation / validate-kubernetes (push) Failing after 11s
Validation / validate-smart-contracts (push) Failing after 11s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 21s
Replace Binance-style dashboard chrome with a shared telemetry UI, SVG icons, and mission headers across Central Bank, Office 24, trade, swap, and admin screens. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,9 @@ import { CONTRACT_ADDRESSES } from '../../config/contracts'
|
||||
import { MAINNET_TETHER_ABI } from '../../abis/MainnetTether'
|
||||
import { subscribeToContractEvents } from '../../utils/contractEvents'
|
||||
import toast from 'react-hot-toast'
|
||||
import DashboardStatCard from '../../features/omnl-dashboard/DashboardStatCard'
|
||||
import DashboardSection from '../../features/omnl-dashboard/DashboardSection'
|
||||
import NasaIcon, { StatusIcon } from '../icons/NasaIcon'
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const { adminActions, auditLogs } = useAdmin()
|
||||
@@ -68,7 +71,7 @@ export default function AdminDashboard() {
|
||||
'Paused',
|
||||
(_event) => {
|
||||
setMainnetTetherPaused(true)
|
||||
toast.success('MainnetTether paused event detected', { icon: '🔔' })
|
||||
toast.success('MainnetTether paused event detected')
|
||||
}
|
||||
).then((unsub) => {
|
||||
unsubscribePaused = unsub
|
||||
@@ -81,7 +84,7 @@ export default function AdminDashboard() {
|
||||
'Unpaused',
|
||||
(_event) => {
|
||||
setMainnetTetherPaused(false)
|
||||
toast.success('MainnetTether unpaused event detected', { icon: '🔔' })
|
||||
toast.success('MainnetTether unpaused event detected')
|
||||
}
|
||||
).then((unsub) => {
|
||||
unsubscribeUnpaused = unsub
|
||||
@@ -96,124 +99,77 @@ export default function AdminDashboard() {
|
||||
}, [publicClient, address])
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Stats Grid */}
|
||||
<div className="space-y-6 nasa-mission-root">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="bg-black/20 rounded-xl p-4 border border-white/10">
|
||||
<div className="text-white/70 text-sm mb-1">Total Actions</div>
|
||||
<div className="text-2xl font-bold text-white">{stats.totalActions}</div>
|
||||
</div>
|
||||
<div className="bg-black/20 rounded-xl p-4 border border-white/10">
|
||||
<div className="text-white/70 text-sm mb-1">Pending</div>
|
||||
<div className="text-2xl font-bold text-yellow-400">{stats.pending}</div>
|
||||
</div>
|
||||
<div className="bg-black/20 rounded-xl p-4 border border-white/10">
|
||||
<div className="text-white/70 text-sm mb-1">Executed</div>
|
||||
<div className="text-2xl font-bold text-green-400">{stats.executed}</div>
|
||||
</div>
|
||||
<div className="bg-black/20 rounded-xl p-4 border border-white/10">
|
||||
<div className="text-white/70 text-sm mb-1">Success Rate</div>
|
||||
<div className="text-2xl font-bold text-blue-400">{stats.successRate}%</div>
|
||||
</div>
|
||||
<DashboardStatCard label="Total actions" value={String(stats.totalActions)} accent="blue" icon="admin" />
|
||||
<DashboardStatCard label="Pending" value={String(stats.pending)} accent="gold" icon="alert" />
|
||||
<DashboardStatCard label="Executed" value={String(stats.executed)} accent="green" icon="check" />
|
||||
<DashboardStatCard label="Success rate" value={`${stats.successRate}%`} accent="blue" icon="chart" />
|
||||
</div>
|
||||
|
||||
{/* Contract Status */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="bg-black/20 rounded-xl p-4 border border-white/10">
|
||||
<div className="dash-card">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="text-white/70 text-sm">MainnetTether</div>
|
||||
<span
|
||||
className={`px-3 py-1 rounded text-xs font-semibold ${
|
||||
mainnetTetherPaused
|
||||
? 'bg-red-500/20 text-red-300'
|
||||
: 'bg-green-500/20 text-green-300'
|
||||
}`}
|
||||
>
|
||||
<div className="dash-stat-label flex items-center gap-1">
|
||||
<NasaIcon name="bridge" size={14} className="nasa-icon--telemetry" />
|
||||
MainnetTether
|
||||
</div>
|
||||
<span className={`nasa-badge ${mainnetTetherPaused ? 'nasa-badge--caution' : 'nasa-badge--nominal'}`}>
|
||||
{mainnetTetherPaused ? 'Paused' : 'Active'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-white/60 text-xs font-mono">
|
||||
{CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}
|
||||
</p>
|
||||
<p className="text-[#7a9bb8] text-xs font-mono">{CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Recent Actions */}
|
||||
<div className="bg-black/20 rounded-xl p-6 border border-white/10">
|
||||
<h3 className="text-lg font-bold text-white mb-4">Recent Actions</h3>
|
||||
<DashboardSection title="Recent actions" icon="rocket">
|
||||
{recentActions.length === 0 ? (
|
||||
<p className="text-white/60 text-sm">No actions yet</p>
|
||||
<p className="text-[#7a9bb8] text-sm">No actions yet</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{recentActions.map((action) => (
|
||||
<div
|
||||
key={action.id}
|
||||
className="bg-white/5 rounded-lg p-3 border border-white/10"
|
||||
>
|
||||
<div key={action.id} className="nasa-inset-panel">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<p className="text-white font-semibold text-sm">{action.type}</p>
|
||||
<p className="text-white/60 text-xs font-mono">
|
||||
{action.contractAddress.slice(0, 20)}...
|
||||
</p>
|
||||
<p className="text-[#e8f4ff] font-semibold text-sm font-mono">{action.type}</p>
|
||||
<p className="text-[#7a9bb8] text-xs font-mono">{action.contractAddress.slice(0, 20)}...</p>
|
||||
</div>
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs ${
|
||||
action.status === TransactionRequestStatus.SUCCESS
|
||||
? 'bg-green-500/20 text-green-300'
|
||||
: action.status === TransactionRequestStatus.FAILED
|
||||
? 'bg-red-500/20 text-red-300'
|
||||
: 'bg-yellow-500/20 text-yellow-300'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center gap-1 text-xs font-mono">
|
||||
<StatusIcon ok={action.status === TransactionRequestStatus.SUCCESS} />
|
||||
{action.status}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-white/50 text-xs mt-2">
|
||||
{new Date(action.createdAt).toLocaleString()}
|
||||
</p>
|
||||
<p className="text-[#7a9bb8] text-xs mt-2">{new Date(action.createdAt).toLocaleString()}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DashboardSection>
|
||||
|
||||
{/* Recent Audit Logs */}
|
||||
<div className="bg-black/20 rounded-xl p-6 border border-white/10">
|
||||
<h3 className="text-lg font-bold text-white mb-4">Recent Audit Logs</h3>
|
||||
<DashboardSection title="Recent audit logs" icon="ledger">
|
||||
{recentLogs.length === 0 ? (
|
||||
<p className="text-white/60 text-sm">No audit logs yet</p>
|
||||
<p className="text-[#7a9bb8] text-sm">No audit logs yet</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{recentLogs.map((log) => (
|
||||
<div
|
||||
key={log.id}
|
||||
className="bg-white/5 rounded-lg p-3 border border-white/10"
|
||||
>
|
||||
<div key={log.id} className="nasa-inset-panel">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<p className="text-white font-semibold text-sm">{log.action}</p>
|
||||
<p className="text-white/60 text-xs font-mono">{log.user.slice(0, 10)}...</p>
|
||||
<p className="text-[#e8f4ff] font-semibold text-sm font-mono">{log.action}</p>
|
||||
<p className="text-[#7a9bb8] text-xs font-mono">{log.user.slice(0, 10)}...</p>
|
||||
</div>
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs ${
|
||||
log.status === 'success'
|
||||
? 'bg-green-500/20 text-green-300'
|
||||
: 'bg-red-500/20 text-red-300'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center gap-1 text-xs font-mono">
|
||||
<StatusIcon ok={log.status === 'success'} />
|
||||
{log.status}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-white/50 text-xs mt-2">
|
||||
{new Date(log.timestamp).toLocaleString()}
|
||||
</p>
|
||||
<p className="text-[#7a9bb8] text-xs mt-2">{new Date(log.timestamp).toLocaleString()}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DashboardSection>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAccount } from 'wagmi';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useAccount } from 'wagmi';
|
||||
import DashboardSection from '../../features/omnl-dashboard/DashboardSection';
|
||||
import NasaIcon, { StatusIcon } from '../icons/NasaIcon';
|
||||
import NasaPageHeader from '../../features/omnl-dashboard/NasaPageHeader';
|
||||
|
||||
interface ChainMetadata {
|
||||
chainId: number;
|
||||
@@ -37,13 +40,6 @@ export default function ChainManagementDashboard() {
|
||||
const loadChains = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// TODO: Connect to ChainRegistry contract
|
||||
// const provider = new ethers.JsonRpcProvider(process.env.NEXT_PUBLIC_RPC_URL);
|
||||
// const registry = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, provider);
|
||||
// const [evmChainIds, evmChains] = await registry.getAllEVMChains();
|
||||
// const [nonEvmIds, nonEvmChains] = await registry.getAllNonEVMChains();
|
||||
|
||||
// Mock data for now
|
||||
setChains([
|
||||
{
|
||||
chainId: 138,
|
||||
@@ -75,7 +71,6 @@ export default function ChainManagementDashboard() {
|
||||
|
||||
const toggleChain = async (currentStatus: boolean) => {
|
||||
try {
|
||||
// TODO: Call ChainRegistry.setChainActive()
|
||||
toast.success(`Chain ${currentStatus ? 'disabled' : 'enabled'}`);
|
||||
loadChains();
|
||||
} catch (error: unknown) {
|
||||
@@ -85,52 +80,56 @@ export default function ChainManagementDashboard() {
|
||||
|
||||
if (!isConnected) {
|
||||
return (
|
||||
<div className="p-6 bg-white/10 rounded-xl">
|
||||
<p className="text-white/70">Please connect your wallet to manage chains.</p>
|
||||
<div className="dash-card nasa-mission-root">
|
||||
<p className="text-[#7a9bb8] flex items-center gap-2">
|
||||
<NasaIcon name="wallet" size={16} className="nasa-icon--telemetry" />
|
||||
Please connect your wallet to manage chains.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-black/20 rounded-xl p-6 border border-white/10">
|
||||
<h2 className="text-xl font-bold text-white mb-4">Chain Management</h2>
|
||||
|
||||
<div className="space-y-6 nasa-mission-root">
|
||||
<NasaPageHeader
|
||||
icon="network"
|
||||
missionId="NET-ADM"
|
||||
title="Chain Management"
|
||||
subtitle="Supported chains, adapters, and deployment registry"
|
||||
/>
|
||||
|
||||
<DashboardSection title="Registered chains" icon="globe">
|
||||
{loading ? (
|
||||
<div className="text-white/70">Loading chains...</div>
|
||||
<p className="text-[#7a9bb8] flex items-center gap-2">
|
||||
<NasaIcon name="loading" size={14} className="nasa-icon--spin nasa-icon--telemetry" />
|
||||
Loading chains…
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{chains.map((chain) => (
|
||||
<div
|
||||
key={chain.chainId || chain.chainIdentifier}
|
||||
className="bg-white/5 rounded-lg p-4 flex items-center justify-between"
|
||||
>
|
||||
<div key={chain.chainId || chain.chainIdentifier} className="nasa-inset-panel flex items-center justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h3 className="text-white font-semibold">
|
||||
<h3 className="text-[#e8f4ff] font-semibold font-mono flex items-center gap-2">
|
||||
<NasaIcon name="network" size={14} className="nasa-icon--telemetry" />
|
||||
{chain.chainIdentifier} ({chain.chainType})
|
||||
</h3>
|
||||
<p className="text-white/70 text-sm">
|
||||
Adapter: {chain.adapter.slice(0, 10)}...
|
||||
</p>
|
||||
<p className="text-white/70 text-sm">
|
||||
Explorer: <a href={chain.explorerUrl} target="_blank" rel="noopener noreferrer" className="text-blue-400 hover:underline">{chain.explorerUrl}</a>
|
||||
<p className="text-[#7a9bb8] text-sm font-mono">Adapter: {chain.adapter.slice(0, 10)}…</p>
|
||||
<p className="text-[#7a9bb8] text-sm">
|
||||
Explorer:{' '}
|
||||
<a href={chain.explorerUrl} target="_blank" rel="noopener noreferrer" className="text-[#00d4ff] hover:underline inline-flex items-center gap-1">
|
||||
<NasaIcon name="link" size={12} />
|
||||
{chain.explorerUrl}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className={`px-3 py-1 rounded-full text-sm ${
|
||||
chain.isActive
|
||||
? 'bg-green-500/20 text-green-200'
|
||||
: 'bg-red-500/20 text-red-200'
|
||||
}`}>
|
||||
<span className={`nasa-badge flex items-center gap-1 ${chain.isActive ? 'nasa-badge--nominal' : 'nasa-badge--caution'}`}>
|
||||
<StatusIcon ok={chain.isActive} />
|
||||
{chain.isActive ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleChain(chain.isActive)}
|
||||
className={`px-4 py-2 rounded-lg font-semibold transition-colors ${
|
||||
chain.isActive
|
||||
? 'bg-red-600 hover:bg-red-700 text-white'
|
||||
: 'bg-green-600 hover:bg-green-700 text-white'
|
||||
}`}
|
||||
className={`nasa-btn ${chain.isActive ? '' : 'nasa-btn--primary'}`}
|
||||
>
|
||||
{chain.isActive ? 'Disable' : 'Enable'}
|
||||
</button>
|
||||
@@ -139,17 +138,16 @@ export default function ChainManagementDashboard() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DashboardSection>
|
||||
|
||||
<div className="bg-black/20 rounded-xl p-6 border border-white/10">
|
||||
<h3 className="text-lg font-bold text-white mb-4">Add New Chain</h3>
|
||||
<DashboardSection title="Add new chain" icon="rocket">
|
||||
<div className="space-y-4">
|
||||
<select
|
||||
value={selectedChain}
|
||||
onChange={(e) => setSelectedChain(e.target.value)}
|
||||
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white"
|
||||
className="nasa-select"
|
||||
>
|
||||
<option value="">Select chain to add...</option>
|
||||
<option value="">Select chain to add…</option>
|
||||
<option value="polygon">Polygon</option>
|
||||
<option value="arbitrum">Arbitrum</option>
|
||||
<option value="optimism">Optimism</option>
|
||||
@@ -159,13 +157,14 @@ export default function ChainManagementDashboard() {
|
||||
<option value="ethereum">Ethereum Mainnet</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={() => toast('Deployment feature coming soon', { icon: 'ℹ️' })}
|
||||
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-semibold"
|
||||
onClick={() => toast('Deployment feature coming soon')}
|
||||
className="nasa-btn nasa-btn--primary w-full"
|
||||
>
|
||||
Deploy Chain Adapter
|
||||
<NasaIcon name="rocket" size={14} />
|
||||
Deploy chain adapter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
204
frontend-dapp/src/components/icons/NasaIcon.tsx
Normal file
204
frontend-dapp/src/components/icons/NasaIcon.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
import type { ReactNode, SVGProps } from 'react';
|
||||
|
||||
export type NasaIconName =
|
||||
| 'central-bank'
|
||||
| 'office'
|
||||
| 'trade'
|
||||
| 'swap'
|
||||
| 'bridge'
|
||||
| 'refresh'
|
||||
| 'check'
|
||||
| 'x'
|
||||
| 'alert'
|
||||
| 'link'
|
||||
| 'arrow-right'
|
||||
| 'network'
|
||||
| 'satellite'
|
||||
| 'ledger'
|
||||
| 'rocket'
|
||||
| 'search'
|
||||
| 'loading'
|
||||
| 'chart'
|
||||
| 'wallet'
|
||||
| 'globe'
|
||||
| 'money'
|
||||
| 'rail'
|
||||
| 'admin';
|
||||
|
||||
type Props = SVGProps<SVGSVGElement> & {
|
||||
name: NasaIconName;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
const paths: Record<NasaIconName, ReactNode> = {
|
||||
'central-bank': (
|
||||
<>
|
||||
<path d="M3 21h18" />
|
||||
<path d="M5 21V7l7-4 7 4v14" />
|
||||
<path d="M9 21v-6h6v6" />
|
||||
<path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01" />
|
||||
</>
|
||||
),
|
||||
office: (
|
||||
<>
|
||||
<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18" />
|
||||
<path d="M6 12h12M10 6h.01M14 6h.01M10 10h.01M14 10h.01M10 14h.01M14 14h.01M10 18h.01M14 18h.01" />
|
||||
</>
|
||||
),
|
||||
trade: (
|
||||
<>
|
||||
<path d="M3 3v18h18" />
|
||||
<path d="M7 14l4-4 3 3 5-6" />
|
||||
</>
|
||||
),
|
||||
swap: (
|
||||
<>
|
||||
<path d="M7 16V4M7 4 3 8M7 4l4 4" />
|
||||
<path d="M17 8v12M17 20l4-4M17 20l-4-4" />
|
||||
</>
|
||||
),
|
||||
bridge: (
|
||||
<>
|
||||
<path d="M4 16h16" />
|
||||
<path d="M6 16V8a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8" />
|
||||
<path d="M8 12h8" />
|
||||
</>
|
||||
),
|
||||
refresh: (
|
||||
<>
|
||||
<path d="M21 12a9 9 0 1 1-2.64-6.36" />
|
||||
<path d="M21 3v6h-6" />
|
||||
</>
|
||||
),
|
||||
check: <path d="M20 6 9 17l-5-5" />,
|
||||
x: (
|
||||
<>
|
||||
<path d="M18 6 6 18" />
|
||||
<path d="m6 6 12 12" />
|
||||
</>
|
||||
),
|
||||
alert: (
|
||||
<>
|
||||
<path d="M12 9v4" />
|
||||
<path d="M12 17h.01" />
|
||||
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||||
</>
|
||||
),
|
||||
link: (
|
||||
<>
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
||||
</>
|
||||
),
|
||||
'arrow-right': (
|
||||
<>
|
||||
<path d="M5 12h14" />
|
||||
<path d="m12 5 7 7-7 7" />
|
||||
</>
|
||||
),
|
||||
network: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="2" />
|
||||
<path d="M12 2v4M12 18v4M2 12h4M18 12h4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
|
||||
</>
|
||||
),
|
||||
satellite: (
|
||||
<>
|
||||
<path d="M13 7 9 3 5 7l4 4" />
|
||||
<path d="m17 11 4 4-4 4-4-4" />
|
||||
<path d="m8 12 4 4 6-6-4-4Z" />
|
||||
<path d="m16 8 3-3M8 16l-3 3" />
|
||||
</>
|
||||
),
|
||||
ledger: (
|
||||
<>
|
||||
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
||||
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
||||
<path d="M8 7h8M8 11h8M8 15h5" />
|
||||
</>
|
||||
),
|
||||
rocket: (
|
||||
<>
|
||||
<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" />
|
||||
<path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" />
|
||||
<path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" />
|
||||
</>
|
||||
),
|
||||
search: (
|
||||
<>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
</>
|
||||
),
|
||||
loading: <path d="M21 12a9 9 0 1 1-6.219-8.56" />,
|
||||
chart: (
|
||||
<>
|
||||
<path d="M3 3v18h18" />
|
||||
<path d="M18 17V9M13 17V5M8 17v-3" />
|
||||
</>
|
||||
),
|
||||
wallet: (
|
||||
<>
|
||||
<path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" />
|
||||
<path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" />
|
||||
</>
|
||||
),
|
||||
globe: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</>
|
||||
),
|
||||
money: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8" />
|
||||
<path d="M12 6v2M12 16v2" />
|
||||
</>
|
||||
),
|
||||
rail: (
|
||||
<>
|
||||
<path d="M2 17h20" />
|
||||
<path d="M6 17V5h12v12" />
|
||||
<path d="M6 9h12M10 13h4" />
|
||||
</>
|
||||
),
|
||||
admin: (
|
||||
<>
|
||||
<path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
export default function NasaIcon({ name, size = 18, className = '', ...props }: Props) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={`nasa-icon ${className}`.trim()}
|
||||
aria-hidden={props['aria-label'] ? undefined : true}
|
||||
{...props}
|
||||
>
|
||||
{paths[name]}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function StatusIcon({ ok, className = '' }: { ok: boolean; className?: string }) {
|
||||
return (
|
||||
<NasaIcon
|
||||
name={ok ? 'check' : 'x'}
|
||||
size={14}
|
||||
className={ok ? `nasa-icon--nominal ${className}` : `nasa-icon--critical ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,61 +1,60 @@
|
||||
import { Link, Outlet, useLocation } from 'react-router-dom';
|
||||
import { useRef } from 'react';
|
||||
import WalletConnect from '../wallet/WalletConnect';
|
||||
import WalletDisconnectNotice from '../wallet/WalletDisconnectNotice';
|
||||
|
||||
const PRODUCTS = [
|
||||
{ path: '/central-bank', label: 'Central Bank', short: 'CB' },
|
||||
{ path: '/office-24', label: 'Office 24', short: 'O24' },
|
||||
{ path: '/trade', label: 'DBIS Trade', short: 'Trade' },
|
||||
{ path: '/swap', label: 'Swap', short: 'Swap' },
|
||||
] as const;
|
||||
|
||||
export default function OmnlProductLayout() {
|
||||
const location = useLocation();
|
||||
const userInitiatedDisconnectRef = useRef(false);
|
||||
|
||||
const isActive = (path: string) => location.pathname.startsWith(path);
|
||||
|
||||
return (
|
||||
<div className="omnl-app min-h-screen flex flex-col bg-[#0b0e11]">
|
||||
<WalletDisconnectNotice userInitiatedDisconnectRef={userInitiatedDisconnectRef} />
|
||||
<header className="omnl-app-header sticky top-0 z-50 border-b border-[#2b3139] bg-[#181a20]">
|
||||
<div className="max-w-[1600px] mx-auto px-4 h-14 flex items-center gap-4">
|
||||
<Link to="/central-bank" className="flex items-center gap-2 shrink-0">
|
||||
<span className="w-8 h-8 rounded bg-[#f0b90b] text-[#181a20] font-bold flex items-center justify-center text-sm">
|
||||
O
|
||||
</span>
|
||||
<span className="font-semibold text-[#eaecef] hidden sm:inline">OMNL</span>
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-1 flex-1" aria-label="OMNL products">
|
||||
{PRODUCTS.map(({ path, label }) => (
|
||||
<Link
|
||||
key={path}
|
||||
to={path}
|
||||
className={`px-4 py-2 rounded text-sm font-medium transition-colors ${
|
||||
isActive(path)
|
||||
? 'bg-[#2b3139] text-[#f0b90b]'
|
||||
: 'text-[#848e9c] hover:text-[#eaecef] hover:bg-[#1e2329]'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Link
|
||||
to="/"
|
||||
className="text-xs text-[#848e9c] hover:text-[#f0b90b] hidden md:inline shrink-0"
|
||||
>
|
||||
Bridge →
|
||||
</Link>
|
||||
<WalletConnect onBeforeDisconnect={() => { userInitiatedDisconnectRef.current = true; }} />
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { Link, Outlet, useLocation } from 'react-router-dom';
|
||||
|
||||
import { useRef } from 'react';
|
||||
|
||||
import WalletConnect from '../wallet/WalletConnect';
|
||||
|
||||
import WalletDisconnectNotice from '../wallet/WalletDisconnectNotice';
|
||||
|
||||
import NasaIcon, { type NasaIconName } from '../icons/NasaIcon';
|
||||
|
||||
|
||||
|
||||
const PRODUCTS: { path: string; label: string; icon: NasaIconName }[] = [
|
||||
|
||||
{ path: '/central-bank', label: 'Central Bank', icon: 'central-bank' },
|
||||
|
||||
{ path: '/office-24', label: 'Office 24', icon: 'office' },
|
||||
|
||||
{ path: '/trade', label: 'DBIS Trade', icon: 'trade' },
|
||||
|
||||
{ path: '/swap', label: 'Digital Swap', icon: 'swap' },
|
||||
|
||||
];
|
||||
|
||||
|
||||
|
||||
export default function OmnlProductLayout() {
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const userInitiatedDisconnectRef = useRef(false);
|
||||
|
||||
|
||||
|
||||
const isActive = (path: string) => location.pathname.startsWith(path);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<div className="omnl-app min-h-screen flex flex-col">
|
||||
|
||||
<WalletDisconnectNotice userInitiatedDisconnectRef={userInitiatedDisconnectRef} />
|
||||
|
||||
<header className="omnl-app-header sticky top-0 z-50 border-b">
|
||||
|
||||
<div className="max-w-[1600px] mx-auto px-4 h-14 flex items-center gap-4">
|
||||
|
||||
<Link to="/central-bank" className="flex items-center gap-2 shrink-0">
|
||||
|
||||
<span className="w-8 h-8 border border-[#1e4a8a] bg-[#0a1224] text-[#00d4ff] font-bold flex items-center justify-center text-sm">
|
||||
|
||||
<NasaIcon name="satellite" size={16} className="nasa-icon--telemetry" />
|
||||
|
||||
</span>
|
||||
|
||||
<span className="nasa-nav-brand font-semibold hidden sm:inline text-sm">OMNL Mission Control</span>
|
||||
|
||||
</Link>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user