diff --git a/frontend-dapp/src/App.tsx b/frontend-dapp/src/App.tsx index fd32c86..3068c1e 100644 --- a/frontend-dapp/src/App.tsx +++ b/frontend-dapp/src/App.tsx @@ -53,10 +53,10 @@ function App() { } /> } /> } /> + } /> }> } /> - } /> } /> } /> } /> diff --git a/frontend-dapp/src/components/admin/AdminDashboard.tsx b/frontend-dapp/src/components/admin/AdminDashboard.tsx index 9491b4e..de7936f 100644 --- a/frontend-dapp/src/components/admin/AdminDashboard.tsx +++ b/frontend-dapp/src/components/admin/AdminDashboard.tsx @@ -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 ( -
- {/* Stats Grid */} +
-
-
Total Actions
-
{stats.totalActions}
-
-
-
Pending
-
{stats.pending}
-
-
-
Executed
-
{stats.executed}
-
-
-
Success Rate
-
{stats.successRate}%
-
+ + + +
- {/* Contract Status */}
-
+
-
MainnetTether
- +
+ + MainnetTether +
+ {mainnetTetherPaused ? 'Paused' : 'Active'}
-

- {CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER} -

+

{CONTRACT_ADDRESSES.mainnet.MAINNET_TETHER}

- {/* Recent Actions */} -
-

Recent Actions

+ {recentActions.length === 0 ? ( -

No actions yet

+

No actions yet

) : (
{recentActions.map((action) => ( -
+
-

{action.type}

-

- {action.contractAddress.slice(0, 20)}... -

+

{action.type}

+

{action.contractAddress.slice(0, 20)}...

- + + {action.status}
-

- {new Date(action.createdAt).toLocaleString()} -

+

{new Date(action.createdAt).toLocaleString()}

))}
)} -
+
- {/* Recent Audit Logs */} -
-

Recent Audit Logs

+ {recentLogs.length === 0 ? ( -

No audit logs yet

+

No audit logs yet

) : (
{recentLogs.map((log) => ( -
+
-

{log.action}

-

{log.user.slice(0, 10)}...

+

{log.action}

+

{log.user.slice(0, 10)}...

- + + {log.status}
-

- {new Date(log.timestamp).toLocaleString()} -

+

{new Date(log.timestamp).toLocaleString()}

))}
)} -
+
) diff --git a/frontend-dapp/src/components/admin/chain-management/ChainManagementDashboard.tsx b/frontend-dapp/src/components/admin/chain-management/ChainManagementDashboard.tsx index 77414d6..cac73ad 100644 --- a/frontend-dapp/src/components/admin/chain-management/ChainManagementDashboard.tsx +++ b/frontend-dapp/src/components/admin/chain-management/ChainManagementDashboard.tsx @@ -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 ( -
-

Please connect your wallet to manage chains.

+
+

+ + Please connect your wallet to manage chains. +

); } return ( -
-
-

Chain Management

- +
+ + + {loading ? ( -
Loading chains...
+

+ + Loading chains… +

) : (
{chains.map((chain) => ( -
+
-

+

+ {chain.chainIdentifier} ({chain.chainType})

-

- Adapter: {chain.adapter.slice(0, 10)}... -

-

- Explorer: {chain.explorerUrl} +

Adapter: {chain.adapter.slice(0, 10)}…

+

+ Explorer:{' '} + + + {chain.explorerUrl} +

- + + {chain.isActive ? 'Active' : 'Inactive'} @@ -139,17 +138,16 @@ export default function ChainManagementDashboard() { ))}
)} -
+ -
-

Add New Chain

+
-
+
); } diff --git a/frontend-dapp/src/components/icons/NasaIcon.tsx b/frontend-dapp/src/components/icons/NasaIcon.tsx new file mode 100644 index 0000000..e587240 --- /dev/null +++ b/frontend-dapp/src/components/icons/NasaIcon.tsx @@ -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 & { + name: NasaIconName; + size?: number; +}; + +const paths: Record = { + '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: ( + <> + + + + ), +}; + +export default function NasaIcon({ name, size = 18, className = '', ...props }: Props) { + return ( + + {paths[name]} + + ); +} + +export function StatusIcon({ ok, className = '' }: { ok: boolean; className?: string }) { + return ( + + ); +} diff --git a/frontend-dapp/src/components/layout/OmnlProductLayout.tsx b/frontend-dapp/src/components/layout/OmnlProductLayout.tsx index 6239a2d..e37d7eb 100644 --- a/frontend-dapp/src/components/layout/OmnlProductLayout.tsx +++ b/frontend-dapp/src/components/layout/OmnlProductLayout.tsx @@ -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 ( -
- -
-
- - - O - - OMNL - - - - - - Bridge → - - { userInitiatedDisconnectRef.current = true; }} /> -
-
-
- -
-
- ); -} +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 ( +
+ +
+
+ + + + + OMNL Mission Control + + + + + + + Bridge + + { userInitiatedDisconnectRef.current = true; }} /> +
+
+
+ +
+
+ ); +} diff --git a/frontend-dapp/src/features/central-bank/CentralBankDashboard.tsx b/frontend-dapp/src/features/central-bank/CentralBankDashboard.tsx index 2d46eb6..143a63d 100644 --- a/frontend-dapp/src/features/central-bank/CentralBankDashboard.tsx +++ b/frontend-dapp/src/features/central-bank/CentralBankDashboard.tsx @@ -1,191 +1,147 @@ -import { Link } from 'react-router-dom'; -import { useCentralBank } from './useCentralBank'; -import ChainMatrixPanel from './ChainMatrixPanel'; -import FineractLedgerBanner from '../omnl-dashboard/FineractLedgerBanner'; -import DashboardStatCard from '../omnl-dashboard/DashboardStatCard'; -import DashboardSection from '../omnl-dashboard/DashboardSection'; -import QuickPortalLinks from '../omnl-dashboard/QuickPortalLinks'; -import { formatUsd } from '../../utils/formatMoney'; - -export default function CentralBankDashboard() { - const cb = useCentralBank(); - const office = cb.office as { - officeId?: number; - name?: string; - externalId?: string; - settlement?: { moneyLayers?: string[]; glM0?: string; glM1?: string; glM2?: string }; - } | null; - - return ( -
-
-
- OMNL Central Bank - Production - 128 chains -
-

Central Bank Operations

-

- Live money supply for {office?.name ?? 'HOSPITALLERS Ali Iraq-Iran'} (Office{' '} - {office?.officeId ?? 24}) · settlement · SWIFT · HYBX · Chain 138 -

-
- - - - {cb.error && ( -
-

Failed to load dashboard

-

{cb.error}

-
- )} - - - -

Money supply

-

- Meta-fiat layers from Fineract general ledger · USD -

- -
- - - - -
- -
- cb.refresh()} - disabled={cb.loading} - > - {cb.loading ? 'Refreshing…' : 'Refresh'} - - } - > -
-
-
Status
-
{(cb.health as { status?: string })?.status ?? '—'}
-
-
-
Office
-
- {office?.officeId ?? 24} · {office?.externalId ?? 'HOSPITALLERS-ALI-IRAQ-IRAN'} -
-
-
-
M2 tokens registered
-
{cb.tokenCount}
-
-
-
Active chains
-
- {(cb.health as { chainSupport?: { active?: number; total?: number } })?.chainSupport - ?.active ?? '—'} - {' / '} - {(cb.health as { chainSupport?: { active?: number; total?: number } })?.chainSupport - ?.total ?? 128} -
-
-
-
- - -
- {(office?.settlement?.moneyLayers ?? ['M0', 'M1', 'M2']).map((layer) => ( - - {layer} - - ))} -
-
-
-
M0 reserve
-
GL {office?.settlement?.glM0 ?? '1050'}
-
-
-
M1 liability
-
GL {office?.settlement?.glM1 ?? '2100'}
-
-
-
M2 broad
-
GL {office?.settlement?.glM2 ?? '2200'}
-
-
-
Inter-office (HO)
-
GL 1410
-
-
-

- - Open Office 24 dashboard → - - {' · '} - - DBIS Trade → - -

-
- - -
    - {((cb.monitor as { swaps?: { status: string; tokenIn: string; createdAt: string }[] })?.swaps ?? []) - .slice(0, 8) - .map((s, i) => ( -
  • - {s.status} - {s.tokenIn?.slice(0, 12)}… - {s.createdAt?.slice(0, 16)} -
  • - ))} - {!((cb.monitor as { swaps?: unknown[] })?.swaps?.length) && ( -
  • No swaps recorded yet
  • - )} -
-
- - -
-
- ); -} +import { Link } from 'react-router-dom'; +import { useCentralBank } from './useCentralBank'; +import ChainMatrixPanel from './ChainMatrixPanel'; +import FineractLedgerBanner from '../omnl-dashboard/FineractLedgerBanner'; +import DashboardStatCard from '../omnl-dashboard/DashboardStatCard'; +import DashboardSection from '../omnl-dashboard/DashboardSection'; +import QuickPortalLinks from '../omnl-dashboard/QuickPortalLinks'; +import NasaPageHeader from '../omnl-dashboard/NasaPageHeader'; +import NasaIcon from '../../components/icons/NasaIcon'; +import { formatUsd } from '../../utils/formatMoney'; + +export default function CentralBankDashboard() { + const cb = useCentralBank(); + const office = cb.office as { + officeId?: number; + name?: string; + externalId?: string; + settlement?: { moneyLayers?: string[]; glM0?: string; glM1?: string; glM2?: string }; + } | null; + + return ( +
+ + OMNL Central Bank + Production + 128 chains + + } + /> + + + + {cb.error && ( +
+ +
+

Failed to load dashboard

+

{cb.error}

+
+
+ )} + + + +

+ + Money supply +

+

Meta-fiat layers from Fineract general ledger · USD

+ +
+ + + + +
+ +
+ cb.refresh()} disabled={cb.loading}> + + {cb.loading ? 'Refreshing…' : 'Refresh'} + + } + > +
+
+
Status
+
{(cb.health as { status?: string })?.status ?? '—'}
+
+
+
Office
+
{office?.officeId ?? 24} · {office?.externalId ?? 'HOSPITALLERS-ALI-IRAQ-IRAN'}
+
+
+
M2 tokens registered
+
{cb.tokenCount}
+
+
+
Active chains
+
+ {(cb.health as { chainSupport?: { active?: number; total?: number } })?.chainSupport?.active ?? '—'} + {' / '} + {(cb.health as { chainSupport?: { active?: number; total?: number } })?.chainSupport?.total ?? 128} +
+
+
+
+ + +
+ {(office?.settlement?.moneyLayers ?? ['M0', 'M1', 'M2']).map((layer) => ( + {layer} + ))} +
+
+
M0 reserve
GL {office?.settlement?.glM0 ?? '1050'}
+
M1 liability
GL {office?.settlement?.glM1 ?? '2100'}
+
M2 broad
GL {office?.settlement?.glM2 ?? '2200'}
+
Inter-office (HO)
GL 1410
+
+

+ + Office 24 dashboard + + + DBIS Trade + +

+
+ + +
    + {((cb.monitor as { swaps?: { status: string; tokenIn: string; createdAt: string }[] })?.swaps ?? []) + .slice(0, 8) + .map((s, i) => ( +
  • + {s.status} + {s.tokenIn?.slice(0, 12)}… + {s.createdAt?.slice(0, 16)} +
  • + ))} + {!((cb.monitor as { swaps?: unknown[] })?.swaps?.length) && ( +
  • No swaps recorded yet
  • + )} +
+
+ + +
+
+ ); +} diff --git a/frontend-dapp/src/features/central-bank/ChainMatrixPanel.tsx b/frontend-dapp/src/features/central-bank/ChainMatrixPanel.tsx index e34366b..d99a1b0 100644 --- a/frontend-dapp/src/features/central-bank/ChainMatrixPanel.tsx +++ b/frontend-dapp/src/features/central-bank/ChainMatrixPanel.tsx @@ -1,100 +1,109 @@ -import { useEffect, useState } from 'react'; -import { fetchOmnlChainRegistry, type ChainRegistryResponse, type OmnlChainSummary } from '../../config/supported-chains'; - -type Filter = 'all' | 'active' | 'primary' | 'l2'; - -export default function ChainMatrixPanel() { - const [registry, setRegistry] = useState(null); - const [filter, setFilter] = useState('active'); - const [search, setSearch] = useState(''); - - useEffect(() => { - fetchOmnlChainRegistry().then(setRegistry); - }, []); - - const chains = (registry?.chains ?? []).filter((c) => { - if (filter === 'active' && c.status !== 'active') return false; - if (filter === 'primary' && c.omnlRole !== 'settlement-hub' && c.omnlRole !== 'mirror-target') return false; - if (filter === 'l2' && c.tier !== 'l2') return false; - if (search) { - const q = search.toLowerCase(); - return c.name.toLowerCase().includes(q) || String(c.chainId).includes(q) || c.nativeSymbol.toLowerCase().includes(q); - } - return true; - }); - - return ( -
-
-
-

128-chain network matrix

-

- {registry?.stats?.active ?? 0} active · {registry?.stats?.staged ?? 0} staged ·{' '} - {registry?.stats?.reserved ?? 0} reserved · primary {registry?.primaryChainId ?? 138} -

-
- setSearch(e.target.value)} - className="bg-[#2b3139] border border-[#474d57] rounded px-3 py-1.5 text-sm w-48" - /> -
- -
- {(['all', 'active', 'primary', 'l2'] as Filter[]).map((f) => ( - - ))} -
- -
- - - - - - - - - - - - - {chains.map((c: OmnlChainSummary) => ( - - - - - - - - - ))} - -
IDNetworkRoleStatusSwapBridge
{c.chainId}{c.name}{c.omnlRole} - - {c.status} - - {c.settlement?.swapEnabled ? '✓' : '—'}{c.settlement?.bridgeEnabled ? '✓' : '—'}
-
-
- ); -} +import { useEffect, useState } from 'react'; +import { fetchOmnlChainRegistry, type ChainRegistryResponse, type OmnlChainSummary } from '../../config/supported-chains'; +import NasaIcon, { StatusIcon } from '../../components/icons/NasaIcon'; + +type Filter = 'all' | 'active' | 'primary' | 'l2'; + +export default function ChainMatrixPanel() { + const [registry, setRegistry] = useState(null); + const [filter, setFilter] = useState('active'); + const [search, setSearch] = useState(''); + + useEffect(() => { + fetchOmnlChainRegistry().then(setRegistry); + }, []); + + const chains = (registry?.chains ?? []).filter((c) => { + if (filter === 'active' && c.status !== 'active') return false; + if (filter === 'primary' && c.omnlRole !== 'settlement-hub' && c.omnlRole !== 'mirror-target') return false; + if (filter === 'l2' && c.tier !== 'l2') return false; + if (search) { + const q = search.toLowerCase(); + return c.name.toLowerCase().includes(q) || String(c.chainId).includes(q) || c.nativeSymbol.toLowerCase().includes(q); + } + return true; + }); + + return ( +
+
+
+

+ + 128-chain network matrix +

+

+ {registry?.stats?.active ?? 0} active · {registry?.stats?.staged ?? 0} staged ·{' '} + {registry?.stats?.reserved ?? 0} reserved · primary {registry?.primaryChainId ?? 138} +

+
+
+ + setSearch(e.target.value)} + className="nasa-input !min-h-[36px] !w-48 text-sm" + /> +
+
+ +
+ {(['all', 'active', 'primary', 'l2'] as Filter[]).map((f) => ( + + ))} +
+ +
+ + + + + + + + + + + + + {chains.map((c: OmnlChainSummary) => ( + + + + + + + + + ))} + +
IDNetworkRoleStatusSwapBridge
{c.chainId}{c.name}{c.omnlRole} + + {c.status} + +
+
+
+ ); +} diff --git a/frontend-dapp/src/features/office24/Office24Dashboard.tsx b/frontend-dapp/src/features/office24/Office24Dashboard.tsx index 8a11d6c..b2f294e 100644 --- a/frontend-dapp/src/features/office24/Office24Dashboard.tsx +++ b/frontend-dapp/src/features/office24/Office24Dashboard.tsx @@ -1,166 +1,151 @@ -import { Link } from 'react-router-dom'; -import { useOffice24 } from './useOffice24'; -import FineractLedgerBanner from '../omnl-dashboard/FineractLedgerBanner'; -import DashboardStatCard from '../omnl-dashboard/DashboardStatCard'; -import DashboardSection from '../omnl-dashboard/DashboardSection'; -import QuickPortalLinks from '../omnl-dashboard/QuickPortalLinks'; -import { formatUsd } from '../../utils/formatMoney'; - -export default function Office24Dashboard() { - const { office, tokens, health, moneySupply, loading, refresh } = useOffice24(); - const profile = office as { - officeId?: number; - externalId?: string; - name?: string; - locked?: boolean; - tenantUser?: string; - settlement?: { defaultCurrency?: string; moneyLayers?: string[] }; - rails?: { swift?: { enabled?: boolean }; hybx?: { enabled?: boolean }; chain138?: { enabled?: boolean } }; - } | null; - - return ( -
-
-
-
- Office 24 - {profile?.locked && Single office · locked} -
-

{profile?.name ?? 'HOSPITALLERS Ali Iraq-Iran'}

-

{profile?.externalId ?? 'HOSPITALLERS-ALI-IRAQ-IRAN'}

-
-
- - Central Bank - - - DBIS Trade - -
-
- - - - - -

Office balances

-

- Live Fineract · {profile?.settlement?.defaultCurrency ?? 'USD'} -

- -
- - - - -
- -
- refresh()} className="text-xs text-[#f0b90b] hover:underline"> - {loading ? 'Refreshing…' : 'Refresh'} - - } - > -
-
-
SWIFT
-
- {profile?.rails?.swift?.enabled ? 'Enabled' : 'Off'} -
-
-
-
HYBX
-
- {profile?.rails?.hybx?.enabled ? 'Enabled' : 'Off'} -
-
-
-
Chain 138
-
- {profile?.rails?.chain138?.enabled ? 'Enabled' : 'Off'} -
-
-
-
DBIS Exchange
-
{(health as { status?: string })?.status ?? 'ok'}
-
-
-
Fineract tenant
-
{profile?.tenantUser ?? 'ali_hospitallers_tenant'}
-
-
-
- - -
- - - - - - - - - - - - {tokens.map((t) => ( - - - - - - - - ))} - {!tokens.length && ( - - - - )} - -
SymbolLineSwapConvertTransfer
{t.symbol}{t.omnlLine ?? 'M2'}{t.swappable !== false ? 'Yes' : '—'}{t.convertible !== false ? 'Yes' : '—'} - {t.transferableInternal && t.transferableExternal ? 'Internal + external' : 'Yes'} -
- No M2 tokens loaded -
-
-
-
-
- ); -} +import { Link } from 'react-router-dom'; +import { useOffice24 } from './useOffice24'; +import FineractLedgerBanner from '../omnl-dashboard/FineractLedgerBanner'; +import DashboardStatCard from '../omnl-dashboard/DashboardStatCard'; +import DashboardSection from '../omnl-dashboard/DashboardSection'; +import QuickPortalLinks from '../omnl-dashboard/QuickPortalLinks'; +import NasaPageHeader from '../omnl-dashboard/NasaPageHeader'; +import NasaIcon, { StatusIcon } from '../../components/icons/NasaIcon'; +import { formatUsd } from '../../utils/formatMoney'; + +export default function Office24Dashboard() { + const { office, tokens, health, moneySupply, loading, refresh } = useOffice24(); + const profile = office as { + officeId?: number; + externalId?: string; + name?: string; + locked?: boolean; + tenantUser?: string; + settlement?: { defaultCurrency?: string; moneyLayers?: string[] }; + rails?: { swift?: { enabled?: boolean }; hybx?: { enabled?: boolean }; chain138?: { enabled?: boolean } }; + } | null; + + return ( +
+
+
+ + Office 24 + {profile?.locked && Single office · locked} + + } + /> +
+
+ + Central Bank + + + DBIS Trade + +
+
+ + + + + +

+ + Office balances +

+

Live Fineract · {profile?.settlement?.defaultCurrency ?? 'USD'}

+ +
+ + + + +
+ +
+ refresh()} className="text-xs text-[#00d4ff] hover:underline inline-flex items-center gap-1"> + + {loading ? 'Refreshing…' : 'Refresh'} + + } + > +
+
+
SWIFT
+
+ + {profile?.rails?.swift?.enabled ? 'Enabled' : 'Off'} +
+
+
+
HYBX
+
+ + {profile?.rails?.hybx?.enabled ? 'Enabled' : 'Off'} +
+
+
+
Chain 138
+
+ + {profile?.rails?.chain138?.enabled ? 'Enabled' : 'Off'} +
+
+
+
DBIS Exchange
+
+ + {(health as { status?: string })?.status ?? 'ok'} +
+
+
+
Fineract tenant
+
{profile?.tenantUser ?? 'ali_hospitallers_tenant'}
+
+
+
+ + +
+ + + + + + + + + + + + {tokens.map((t) => ( + + + + + + + + ))} + {!tokens.length && ( + + + + )} + +
SymbolLineSwapConvertTransfer
{t.symbol}{t.omnlLine ?? 'M2'} + +
No M2 tokens loaded
+
+
+
+
+ ); +} diff --git a/frontend-dapp/src/features/omnl-dashboard/DashboardSection.tsx b/frontend-dapp/src/features/omnl-dashboard/DashboardSection.tsx index d2fc89f..5ae82f3 100644 --- a/frontend-dapp/src/features/omnl-dashboard/DashboardSection.tsx +++ b/frontend-dapp/src/features/omnl-dashboard/DashboardSection.tsx @@ -1,28 +1,35 @@ -import type { ReactNode } from 'react'; - -export default function DashboardSection({ - title, - subtitle, - action, - children, - className = '', -}: { - title: string; - subtitle?: string; - action?: React.ReactNode; - children: ReactNode; - className?: string; -}) { - return ( -
-
-
-

{title}

- {subtitle &&

{subtitle}

} -
- {action} -
- {children} -
- ); -} +import type { ReactNode } from 'react'; +import type { NasaIconName } from '../../components/icons/NasaIcon'; +import NasaIcon from '../../components/icons/NasaIcon'; + +export default function DashboardSection({ + title, + subtitle, + action, + children, + className = '', + icon, +}: { + title: string; + subtitle?: string; + action?: ReactNode; + children: ReactNode; + className?: string; + icon?: NasaIconName; +}) { + return ( +
+
+
+

+ {icon && } + {title} +

+ {subtitle &&

{subtitle}

} +
+ {action} +
+ {children} +
+ ); +} diff --git a/frontend-dapp/src/features/omnl-dashboard/DashboardStatCard.tsx b/frontend-dapp/src/features/omnl-dashboard/DashboardStatCard.tsx index c4a6154..a095bb7 100644 --- a/frontend-dapp/src/features/omnl-dashboard/DashboardStatCard.tsx +++ b/frontend-dapp/src/features/omnl-dashboard/DashboardStatCard.tsx @@ -1,28 +1,36 @@ -type Accent = 'gold' | 'green' | 'blue' | 'neutral'; - -const ACCENT: Record = { - blue: 'border-l-[#3861fb]', - green: 'border-l-[#0ecb81]', - gold: 'border-l-[#f0b90b]', - neutral: 'border-l-[#848e9c]', -}; - -export default function DashboardStatCard({ - label, - value, - hint, - accent = 'neutral', -}: { - label: string; - value: string; - hint?: string; - accent?: Accent; -}) { - return ( -
-

{label}

-

{value}

- {hint &&

{hint}

} -
- ); -} +import type { NasaIconName } from '../../components/icons/NasaIcon'; +import NasaIcon from '../../components/icons/NasaIcon'; + +type Accent = 'gold' | 'green' | 'blue' | 'neutral'; + +const ACCENT: Record = { + blue: 'border-l-[#00d4ff]', + green: 'border-l-[#3dff8a]', + gold: 'border-l-[#ffb347]', + neutral: 'border-l-[#7a9bb8]', +}; + +export default function DashboardStatCard({ + label, + value, + hint, + accent = 'neutral', + icon, +}: { + label: string; + value: string; + hint?: string; + accent?: Accent; + icon?: NasaIconName; +}) { + return ( +
+

+ {icon && } + {label} +

+

{value}

+ {hint &&

{hint}

} +
+ ); +} diff --git a/frontend-dapp/src/features/omnl-dashboard/FineractLedgerBanner.tsx b/frontend-dapp/src/features/omnl-dashboard/FineractLedgerBanner.tsx index f2fc766..8555f39 100644 --- a/frontend-dapp/src/features/omnl-dashboard/FineractLedgerBanner.tsx +++ b/frontend-dapp/src/features/omnl-dashboard/FineractLedgerBanner.tsx @@ -1,72 +1,99 @@ -import type { MoneySupply } from '../central-bank/useCentralBank'; - -type Props = { - moneySupply: MoneySupply | null; - loading?: boolean; - officeName?: string; -}; - -function formatAsOf(asOf?: string) { - if (!asOf) return null; - return asOf.slice(0, 19).replace('T', ' ') + ' UTC'; -} - -export default function FineractLedgerBanner({ moneySupply, loading, officeName }: Props) { - if (loading && !moneySupply) { - return ( -
-

Loading live ledger balances…

-

Fetching Fineract data for Office 24

-
- ); - } - - if (!moneySupply) { - return ( -
-

Ledger data not available

-

- The money-supply API did not respond. Check settlement middleware on port 3011. -

-
- ); - } - - const asOf = formatAsOf(moneySupply.asOf); - const officeLabel = officeName ?? `Office ${moneySupply.officeId ?? 24}`; - - if (moneySupply.fineractConnected === false) { - return ( -
-

Fineract offline

-

- Cannot reach Fineract for {officeLabel}. Verify OMNL_FINERACT_* credentials on the - settlement service. -

-
- ); - } - - if (moneySupply.fineractConnected && !moneySupply.fineractLive) { - return ( -
-

Fineract connected — opening journal pending

-

- {officeLabel} has no M1 allocation yet. Post the opening entry: Debit GL 1410 (Due from - Head Office) · Credit GL 2100 (M1 liabilities). - {asOf ? ` · Checked ${asOf}` : ''} -

-
- ); - } - - return ( -
-

Live Fineract ledger · {officeLabel}

-

- M0 / M1 / M2 balances below are read from Fineract in real time. - {asOf ? ` Last updated ${asOf}.` : ''} -

-
- ); -} +import type { MoneySupply } from '../central-bank/useCentralBank'; +import NasaIcon from '../../components/icons/NasaIcon'; + +type Props = { + moneySupply: MoneySupply | null; + loading?: boolean; + officeName?: string; +}; + +function formatAsOf(asOf?: string) { + if (!asOf) return null; + return asOf.slice(0, 19).replace('T', ' ') + ' UTC'; +} + +function BannerIcon({ variant }: { variant: 'ok' | 'warn' | 'error' | 'loading' }) { + const map = { + ok: { name: 'check' as const, className: 'nasa-icon--nominal' }, + warn: { name: 'alert' as const, className: 'nasa-icon--caution' }, + error: { name: 'x' as const, className: 'nasa-icon--critical' }, + loading: { name: 'loading' as const, className: 'nasa-icon--telemetry nasa-icon--spin' }, + }; + const { name, className } = map[variant]; + return ; +} + +export default function FineractLedgerBanner({ moneySupply, loading, officeName }: Props) { + if (loading && !moneySupply) { + return ( +
+ +
+

Loading live ledger balances

+

Fetching Fineract data for Office 24

+
+
+ ); + } + + if (!moneySupply) { + return ( +
+ +
+

Ledger data not available

+

+ The money-supply API did not respond. Check settlement middleware on port 3011. +

+
+
+ ); + } + + const asOf = formatAsOf(moneySupply.asOf); + const officeLabel = officeName ?? `Office ${moneySupply.officeId ?? 24}`; + + if (moneySupply.fineractConnected === false) { + return ( +
+ +
+

Fineract offline

+

+ Cannot reach Fineract for {officeLabel}. Verify OMNL_FINERACT_* credentials on the + settlement service. +

+
+
+ ); + } + + if (moneySupply.fineractConnected && !moneySupply.fineractLive) { + return ( +
+ +
+

Fineract connected — opening journal pending

+

+ {officeLabel} has no M1 allocation yet. Post the opening entry: Debit GL 1410 (Due from + Head Office) · Credit GL 2100 (M1 liabilities). + {asOf ? ` · Checked ${asOf}` : ''} +

+
+
+ ); + } + + return ( +
+ +
+

Live Fineract ledger · {officeLabel}

+

+ M0 / M1 / M2 balances below are read from Fineract in real time. + {asOf ? ` Last updated ${asOf}.` : ''} +

+
+
+ ); +} diff --git a/frontend-dapp/src/features/omnl-dashboard/NasaPageHeader.tsx b/frontend-dapp/src/features/omnl-dashboard/NasaPageHeader.tsx new file mode 100644 index 0000000..4c46818 --- /dev/null +++ b/frontend-dapp/src/features/omnl-dashboard/NasaPageHeader.tsx @@ -0,0 +1,49 @@ +import { useEffect, useState, type ReactNode } from 'react'; +import type { NasaIconName } from '../../components/icons/NasaIcon'; +import NasaIcon from '../../components/icons/NasaIcon'; + +type Props = { + icon: NasaIconName; + missionId: string; + title: string; + subtitle?: string; + badges?: ReactNode; +}; + +function missionClock() { + const now = new Date(); + return now.toISOString().slice(11, 19) + ' UTC'; +} + +export default function NasaPageHeader({ icon, missionId, title, subtitle, badges }: Props) { + const [clock, setClock] = useState(missionClock); + + useEffect(() => { + const id = setInterval(() => setClock(missionClock()), 1000); + return () => clearInterval(id); + }, []); + + return ( +
+
+ + + {missionId} + + MET {clock} +
+
+
+
+ +
+
+

{title}

+ {subtitle &&

{subtitle}

} +
+
+ {badges &&
{badges}
} +
+
+ ); +} diff --git a/frontend-dapp/src/features/omnl-dashboard/QuickPortalLinks.tsx b/frontend-dapp/src/features/omnl-dashboard/QuickPortalLinks.tsx index 67a032c..4c5e795 100644 --- a/frontend-dapp/src/features/omnl-dashboard/QuickPortalLinks.tsx +++ b/frontend-dapp/src/features/omnl-dashboard/QuickPortalLinks.tsx @@ -1,28 +1,28 @@ -const LINKS = [ - { label: 'Central Bank', href: 'https://secure.omdnl.org/central-bank' }, - { label: 'Online banking', href: 'https://online.omdnl.org/central-bank' }, - { label: 'Office 24', href: 'https://office24.omdnl.org/office-24' }, - { label: 'DBIS Trade', href: 'https://exchange.omdnl.org/trade' }, - { label: 'Digital swap', href: 'https://digital.omdnl.org/swap' }, -] as const; - -export default function QuickPortalLinks() { - return ( -
-

Public portals

-
- {LINKS.map(({ label, href }) => ( - - {label} - - ))} -
-
- ); -} +import NasaIcon from '../../components/icons/NasaIcon'; + +const LINKS = [ + { label: 'Central Bank', href: 'https://secure.omdnl.org/central-bank', icon: 'central-bank' as const }, + { label: 'Online banking', href: 'https://online.omdnl.org/central-bank', icon: 'globe' as const }, + { label: 'Office 24', href: 'https://office24.omdnl.org/office-24', icon: 'office' as const }, + { label: 'DBIS Trade', href: 'https://exchange.omdnl.org/trade', icon: 'trade' as const }, + { label: 'Digital swap', href: 'https://digital.omdnl.org/swap', icon: 'swap' as const }, +] as const; + +export default function QuickPortalLinks() { + return ( +
+

+ + Public portals +

+
+ {LINKS.map(({ label, href, icon }) => ( + + + {label} + + ))} +
+
+ ); +} diff --git a/frontend-dapp/src/features/swap/DBISSwapPanel.tsx b/frontend-dapp/src/features/swap/DBISSwapPanel.tsx index bb2f8d3..0de2fab 100644 --- a/frontend-dapp/src/features/swap/DBISSwapPanel.tsx +++ b/frontend-dapp/src/features/swap/DBISSwapPanel.tsx @@ -1,6 +1,8 @@ -import { useEffect } from 'react'; +import { useEffect, type ReactNode } from 'react'; import { useDbisSwap } from './useDbisSwap'; import { formatAmountFromWei } from '../../config/dex'; +import NasaPageHeader from '../omnl-dashboard/NasaPageHeader'; +import NasaIcon from '../../components/icons/NasaIcon'; function TokenSelect({ label, @@ -15,9 +17,9 @@ function TokenSelect({ }) { return ( -
- Slippage +
+ Slippage {[25, 50, 100].map((bps) => (
-
-

Expected output

-

- {outDisplay} {swap.tokenOut.symbol} +

+

Expected output

+

+ {outDisplay} {swap.tokenOut.symbol}

{swap.plan?.plannerResponse?.routePlan?.legs && ( -

+

Route:{' '} {swap.plan.plannerResponse.routePlan.legs .map((l: { provider?: string }) => l.provider) @@ -123,174 +150,107 @@ export default function DBISSwapPanel() { )}

- {swap.error &&

{swap.error}

} + {swap.error && ( +

+ + {swap.error} +

+ )} {swap.txHash && ( -

+

Tx:{' '} - + {swap.txHash}

)}
-
-
-
-
-

OMNL Office 24 ledger

- {!swap.isConnected ? ( -

Connect wallet for crypto balances

- ) : swap.ledger ? ( -
-

- M1 circulating:{' '} - {(swap.ledger as { fundLedger?: { M1?: { circulating?: string } } }).fundLedger?.M1?.circulating ?? '—'} -

-

- M2 broad:{' '} - {(swap.ledger as { fundLedger?: { M2?: { broadMoney?: string } } }).fundLedger?.M2?.broadMoney ?? '—'} -

- {(swap.ledger as { cryptoLedger?: { balances?: Record } }).cryptoLedger?.balances && ( -
-

Wallet balances

- {Object.entries( - (swap.ledger as { cryptoLedger: { balances: Record } }).cryptoLedger.balances, - ).map(([sym, bal]) => ( -

- {sym}: {Number(bal).toFixed(4)} -

- ))} -
- )} -
- ) : ( - + )} + + + +

GL 2200 → on-chain M2

+ swap.setLoadAmount(e.target.value)} className="nasa-input mb-2 text-sm" /> + - )} -
+ -
-

M2 token load

-

Load from M2 fiat (GL 2200) → on-chain · swappable · convertible

- swap.setLoadAmount(e.target.value)} - className="ui-input w-full mb-2 text-sm" - /> - -
- -
-

Transfer

- swap.setTransferTo(e.target.value)} - className="ui-input w-full mb-2 text-sm font-mono" - /> - swap.setTransferAmount(e.target.value)} - className="ui-input w-full mb-2 text-sm" - /> -
- -
- swap.setExternalIban(e.target.value)} - className="ui-input w-full mb-2 text-sm font-mono" - /> - -
+ swap.setExternalIban(e.target.value)} className="nasa-input mb-2 text-sm" /> + + - {swap.lastSettlement && ( -
-

Last settlement

-

- {(swap.lastSettlement as { phase?: string }).phase ?? 'OK'} ·{' '} - {(swap.lastSettlement as { settlementId?: string }).settlementId ?? '—'} -

-
- )} - -
-

Swap monitor

- {swap.monitor ? ( -
-

- Total: {(swap.monitor as { stats?: { total?: number } }).stats?.total ?? 0} · 24h:{' '} - {(swap.monitor as { stats?: { last24h?: number } }).stats?.last24h ?? 0} + {swap.lastSettlement && ( + +

+ {(swap.lastSettlement as { phase?: string }).phase ?? 'OK'} · {(swap.lastSettlement as { settlementId?: string }).settlementId ?? '—'}

-
    - {((swap.monitor as { swaps?: { tokenIn: string; status: string; createdAt: string }[] }).swaps ?? []) - .slice(0, 5) - .map((s, i) => ( -
  • - {s.status} · {s.createdAt.slice(0, 16)} -
  • - ))} -
-
- ) : ( -

Loading…

+ )} + + + {swap.monitor ? ( +
+

Total: {(swap.monitor as { stats?: { total?: number } }).stats?.total ?? 0} · 24h: {(swap.monitor as { stats?: { last24h?: number } }).stats?.last24h ?? 0}

+
    + {((swap.monitor as { swaps?: { tokenIn: string; status: string; createdAt: string }[] }).swaps ?? []).slice(0, 5).map((s, i) => ( +
  • {s.status} · {s.createdAt.slice(0, 16)}
  • + ))} +
+
+ ) : ( +

Loading telemetry…

+ )} +
diff --git a/frontend-dapp/src/features/trade/BinanceTradePanel.tsx b/frontend-dapp/src/features/trade/BinanceTradePanel.tsx index 3a35692..7dbaf44 100644 --- a/frontend-dapp/src/features/trade/BinanceTradePanel.tsx +++ b/frontend-dapp/src/features/trade/BinanceTradePanel.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useDbisSwap } from '../swap/useDbisSwap'; import { formatAmountFromWei, type DexToken } from '../../config/dex'; import TokenIcon from '../../components/ui/TokenIcon'; +import NasaIcon from '../../components/icons/NasaIcon'; type Side = 'buy' | 'sell'; @@ -24,11 +25,12 @@ function MarketRow({ active ? 'trade-market-row--active' : '' }`} > - + + {token.symbol} - /{quote} + /{quote} - M2 + M2 ); } @@ -42,7 +44,7 @@ function OrderBookMock({ price }: { price: string }) { }); return (
-
+
Price Amount
@@ -113,20 +115,24 @@ export default function BinanceTradePanel() { : '—'; return ( -
+
{/* Markets sidebar */} -