chore(frontend): add dashboard styles and ecosystem hub modules for production build
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 25s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 25s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
43
frontend-dapp/src/components/layout/MobileBottomNav.tsx
Normal file
43
frontend-dapp/src/components/layout/MobileBottomNav.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import NasaIcon from '../icons/NasaIcon';
|
||||
|
||||
type Props = {
|
||||
onOpenMenu: () => void;
|
||||
};
|
||||
|
||||
const TABS = [
|
||||
{ to: '/hub', label: 'Home', icon: 'dashboard' as const },
|
||||
{ to: '/bridge', label: 'Bridge', icon: 'bridge' as const },
|
||||
{ to: '/trade', label: 'Trade', icon: 'trade' as const },
|
||||
{ to: '/central-bank', label: 'Z Bank', icon: 'central-bank' as const },
|
||||
];
|
||||
|
||||
export default function MobileBottomNav({ onOpenMenu }: Props) {
|
||||
const location = useLocation();
|
||||
|
||||
const isActive = (path: string) => {
|
||||
if (path === '/hub') return location.pathname === '/hub' || location.pathname === '/';
|
||||
if (path === '/bridge') return location.pathname === '/bridge' || location.pathname.startsWith('/bridge');
|
||||
if (path === '/') return location.pathname === '/';
|
||||
return location.pathname.startsWith(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="dbis-bottom-nav md:hidden" aria-label="Primary navigation">
|
||||
{TABS.map(({ to, label, icon }) => (
|
||||
<Link
|
||||
key={to}
|
||||
to={to}
|
||||
className={`dbis-bottom-nav__item${isActive(to) ? ' dbis-bottom-nav__item--active' : ''}`}
|
||||
>
|
||||
<NasaIcon name={icon} size={20} />
|
||||
<span>{label}</span>
|
||||
</Link>
|
||||
))}
|
||||
<button type="button" className="dbis-bottom-nav__item" onClick={onOpenMenu} aria-label="Open menu">
|
||||
<span className="text-lg leading-none">☰</span>
|
||||
<span>More</span>
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
80
frontend-dapp/src/components/layout/MobileNavDrawer.tsx
Normal file
80
frontend-dapp/src/components/layout/MobileNavDrawer.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export type MobileNavItem = {
|
||||
to?: string;
|
||||
href?: string;
|
||||
label: string;
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
items: MobileNavItem[];
|
||||
};
|
||||
|
||||
export default function MobileNavDrawer({ open, onClose, title, items }: Props) {
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const prev = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => {
|
||||
document.body.style.overflow = prev;
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] lg:hidden" role="dialog" aria-modal="true" aria-label={title}>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-0 bg-black/75 backdrop-blur-sm"
|
||||
aria-label="Close menu"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<nav className="absolute top-0 left-0 bottom-0 w-[min(100vw,20rem)] bg-[#141a16] border-r border-green-500/20 flex flex-col shadow-2xl safe-area-pad">
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10 min-h-[56px]">
|
||||
<span className="text-white font-semibold text-sm">{title}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="text-white/80 hover:text-white p-2 min-h-[44px] min-w-[44px] flex items-center justify-center rounded-lg"
|
||||
aria-label="Close"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<ul className="flex-1 overflow-y-auto py-2">
|
||||
{items.map((item) => {
|
||||
const className = `block w-full text-left px-4 py-3 text-sm font-medium min-h-[44px] ${
|
||||
item.active ? 'bg-green-600/20 text-green-300 border-l-2 border-green-400' : 'text-[#A0A0A0] hover:text-white hover:bg-white/5'
|
||||
}`;
|
||||
if (item.href) {
|
||||
return (
|
||||
<li key={item.href}>
|
||||
<a href={item.href} target="_blank" rel="noopener noreferrer" className={className} onClick={onClose}>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (item.to) {
|
||||
return (
|
||||
<li key={item.to}>
|
||||
<Link to={item.to} className={className} onClick={() => { item.onClick?.(); onClose(); }}>
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
frontend-dapp/src/config/ecosystemLinkRouting.ts
Normal file
16
frontend-dapp/src/config/ecosystemLinkRouting.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { EcosystemLink } from './ecosystemLinks';
|
||||
|
||||
/** Paths served outside the React SPA (static HTML or proxied APIs). */
|
||||
export function isStaticPortalPath(href: string): boolean {
|
||||
return (
|
||||
href.startsWith('/omnl') ||
|
||||
href.startsWith('/api') ||
|
||||
href.startsWith('/settlement') ||
|
||||
href.startsWith('/exchange') ||
|
||||
href.startsWith('/reserve/institutional')
|
||||
);
|
||||
}
|
||||
|
||||
export function ecosystemLinkUsesAnchor(link: EcosystemLink): boolean {
|
||||
return Boolean(link.external) || link.href.startsWith('http') || isStaticPortalPath(link.href);
|
||||
}
|
||||
71
frontend-dapp/src/config/ecosystemLinks.ts
Normal file
71
frontend-dapp/src/config/ecosystemLinks.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { NasaIconName } from '../components/icons/NasaIcon';
|
||||
import { defaultFrontendExplorerUrl } from './networks';
|
||||
|
||||
export type EcosystemLinkAccent = 'green' | 'teal' | 'blue' | 'gold' | 'purple' | 'slate';
|
||||
|
||||
export type EcosystemLink = {
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
icon: NasaIconName;
|
||||
href: string;
|
||||
external?: boolean;
|
||||
accent?: EcosystemLinkAccent;
|
||||
};
|
||||
|
||||
/** In-app React routes (same origin) */
|
||||
export const IN_APP_LINKS: EcosystemLink[] = [
|
||||
{ id: 'bridge', label: 'Bridge', description: 'Cross-chain bridge, CCIP, XRPL, trustless', icon: 'bridge', href: '/bridge', accent: 'teal' },
|
||||
{ id: 'swap', label: 'Digital Swap', description: 'M2 load, token swap, settlement rails', icon: 'swap', href: '/swap', accent: 'blue' },
|
||||
{ id: 'trade', label: 'DBIS Trade', description: 'Markets, order book, spot trading', icon: 'trade', href: '/trade', accent: 'gold' },
|
||||
{ id: 'central-bank', label: 'Z Online Bank', description: 'Zardasht banking · balances & payments', icon: 'central-bank', href: '/central-bank', accent: 'green' },
|
||||
{ id: 'z-full-settlement', label: 'Z M0-M4 settlement', description: 'Full money-supply settlement panel', icon: 'money', href: '/central-bank#z-full-settlement', accent: 'green' },
|
||||
{ id: 'wire-protocols', label: 'Wire protocols', description: 'MT102, MT103 LC, SBLC, BG settlement', icon: 'terminal', href: '/central-bank#z-settlement-protocols', accent: 'gold' },
|
||||
{ id: 'office-24', label: 'Office 24 Central Bank', description: '128-bank corridors · IPSAS settlement', icon: 'office', href: '/office-24', accent: 'purple' },
|
||||
{ id: 'reserve', label: 'Reserve', description: 'Coverage, attestation, peg status', icon: 'money', href: '/reserve', accent: 'green' },
|
||||
{ id: 'history', label: 'History', description: 'Bridge transfer lookup & tracking', icon: 'ledger', href: '/history', accent: 'slate' },
|
||||
{ id: 'wallets', label: 'Wallets', description: 'OneX Wallet · email, social, passkey on Shiva', icon: 'wallet', href: '/wallets', accent: 'blue' },
|
||||
{ id: 'admin', label: 'Admin', description: 'Multisig, channels, bridge operators', icon: 'admin', href: '/admin', accent: 'slate' },
|
||||
{ id: 'docs', label: 'Developers', description: 'API reference & integration', icon: 'code', href: '/docs', accent: 'teal' },
|
||||
{ id: 'hub', label: 'DBIS Dashboard', description: 'All apps, consoles & API links', icon: 'dashboard', href: '/hub', accent: 'green' },
|
||||
];
|
||||
|
||||
/** Primary apps shown in dashboard quick-access strip */
|
||||
export const FEATURED_APP_LINKS: EcosystemLink[] = [
|
||||
IN_APP_LINKS.find((l) => l.id === 'central-bank')!,
|
||||
IN_APP_LINKS.find((l) => l.id === 'bridge')!,
|
||||
IN_APP_LINKS.find((l) => l.id === 'trade')!,
|
||||
IN_APP_LINKS.find((l) => l.id === 'swap')!,
|
||||
IN_APP_LINKS.find((l) => l.id === 'office-24')!,
|
||||
];
|
||||
|
||||
/** Static operator consoles — served from /omnl/* (portal-serve or token-aggregation :3000) */
|
||||
export const OPERATOR_CONSOLE_LINKS: EcosystemLink[] = [
|
||||
{ id: 'compliance', label: 'OMNL Compliance Console', description: 'Posture, pending actions, Safe tx', icon: 'shield', href: '/omnl/compliance', accent: 'purple' },
|
||||
{ id: 'omnl-dash', label: 'OMNL API Snapshot', description: 'Registry and Fineract compare JSON', icon: 'chart', href: '/omnl/dashboard', accent: 'blue' },
|
||||
{ id: 'reserve-dash', label: 'Reserve Dashboard', description: 'Proof rows, coverage, institutional view', icon: 'money', href: '/reserve', accent: 'green' },
|
||||
{ id: 'settlement-term', label: 'Settlement Terminal', description: 'Operator settlement terminal UI', icon: 'terminal', href: '/omnl/terminal', accent: 'gold' },
|
||||
{ id: 'token-admin', label: 'Token Aggregation API', description: 'API catalog JSON', icon: 'ledger', href: '/api/v1/omnl/catalog', accent: 'teal' },
|
||||
];
|
||||
|
||||
/** Public production portals */
|
||||
export const PUBLIC_PORTAL_LINKS: EcosystemLink[] = [
|
||||
{ id: 'pub-bank', label: 'Z Online Bank', description: 'online.omdnl.org', icon: 'globe', href: 'https://online.omdnl.org/central-bank', external: true, accent: 'green' },
|
||||
{ id: 'pub-cb', label: 'Central Bank', description: 'secure.omdnl.org', icon: 'central-bank', href: 'https://secure.omdnl.org/central-bank', external: true, accent: 'green' },
|
||||
{ id: 'pub-o24', label: 'Office 24 Central Bank', description: 'office24.omdnl.org', icon: 'office', href: 'https://office24.omdnl.org/office-24', external: true, accent: 'purple' },
|
||||
{ id: 'pub-trade', label: 'Exchange', description: 'exchange.omdnl.org', icon: 'trade', href: 'https://exchange.omdnl.org/trade', external: true, accent: 'gold' },
|
||||
{ id: 'pub-forex', label: 'Forex', description: 'forex.omdnl.org', icon: 'trade', href: 'https://forex.omdnl.org/trade', external: true, accent: 'gold' },
|
||||
{ id: 'pub-swap', label: 'Digital Swap', description: 'digital.omdnl.org', icon: 'swap', href: 'https://digital.omdnl.org/swap', external: true, accent: 'teal' },
|
||||
{ id: 'explorer', label: 'Chain 138 Explorer', description: 'Blocks, contracts, transactions', icon: 'globe', href: defaultFrontendExplorerUrl, external: true, accent: 'slate' },
|
||||
];
|
||||
|
||||
/** API & docs endpoints — same-origin via portal-serve proxy on tunnel/local */
|
||||
export const API_LINKS: EcosystemLink[] = [
|
||||
{ id: 'openapi', label: 'OpenAPI JSON', description: 'Machine-readable OMNL API spec', icon: 'code', href: '/api/v1/omnl/openapi.json', accent: 'teal' },
|
||||
{ id: 'catalog', label: 'API Catalog', description: 'All OMNL routes and auth notes', icon: 'ledger', href: '/api/v1/omnl/catalog', accent: 'blue' },
|
||||
{ id: 'integration', label: 'Integration Status', description: 'Configured env groups (no secrets)', icon: 'check', href: '/api/v1/omnl/integration-status', accent: 'green' },
|
||||
{ id: 'registry', label: 'IPSAS Registry', description: 'GL registry JSON', icon: 'money', href: '/api/v1/omnl/ipsas/registry', accent: 'gold' },
|
||||
{ id: 'settlement-health', label: 'Settlement health', description: '128-chain settlement middleware', icon: 'check', href: '/settlement/health', accent: 'green' },
|
||||
{ id: 'exchange-health', label: 'Exchange health', description: 'DBIS exchange service', icon: 'check', href: '/exchange/health', accent: 'green' },
|
||||
{ id: 'fineract-swagger', label: 'Fineract Swagger', description: 'omnl.hybxfinance.io', icon: 'chart', href: 'https://omnl.hybxfinance.io/fineract-provider/swagger-ui/index.html', external: true, accent: 'purple' },
|
||||
];
|
||||
24
frontend-dapp/src/config/onexShiva.ts
Normal file
24
frontend-dapp/src/config/onexShiva.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import onexShivaConfig from '../../../config/onex-shiva-integration.v1.json';
|
||||
|
||||
export type OnexShivaConfig = typeof onexShivaConfig;
|
||||
|
||||
export const onexShiva = onexShivaConfig;
|
||||
|
||||
export const SHIVA_ONEX_CHAIN_ID = Number(
|
||||
import.meta.env.VITE_SHIVA_ONEX_CHAIN_ID || onexShiva.chain.chainId,
|
||||
);
|
||||
|
||||
export const shivaOnexRpcUrl =
|
||||
import.meta.env.VITE_RPC_URL_900001 ||
|
||||
import.meta.env.VITE_SHIVA_ONEX_RPC_URL ||
|
||||
onexShiva.chain.rpcDefault;
|
||||
|
||||
export const shivaOnexExplorerUrl =
|
||||
import.meta.env.VITE_SHIVA_ONEX_EXPLORER_URL || onexShiva.chain.explorerUrl;
|
||||
|
||||
export const onexWalletEnabled =
|
||||
import.meta.env.VITE_ENABLE_ONEX_SHIVA_WALLET !== 'false' &&
|
||||
import.meta.env.VITE_ENABLE_ONEX_SHIVA_WALLET !== '0';
|
||||
|
||||
export const ONEX_BANK_OFFICE_ID = onexShiva.fineract.officeId;
|
||||
export const ONEX_HYBX_WALLET_ID = onexShiva.fineract.hybxWalletId;
|
||||
29
frontend-dapp/src/features/hub/DashboardSectionFold.tsx
Normal file
29
frontend-dapp/src/features/hub/DashboardSectionFold.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { NasaIconName } from '../../components/icons/NasaIcon';
|
||||
import NasaIcon from '../../components/icons/NasaIcon';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
icon: NasaIconName;
|
||||
defaultOpen?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function DashboardSectionFold({ title, subtitle, icon, defaultOpen = true, children }: Props) {
|
||||
return (
|
||||
<details className="dbis-section-fold" open={defaultOpen}>
|
||||
<summary className="dbis-section-fold__summary">
|
||||
<span className="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-green-500/10 border border-green-500/25 text-green-400 shrink-0">
|
||||
<NasaIcon name={icon} size={16} />
|
||||
</span>
|
||||
<span>
|
||||
{title}
|
||||
{subtitle && <span className="block text-xs font-normal text-[var(--nasa-muted)] mt-0.5">{subtitle}</span>}
|
||||
</span>
|
||||
<NasaIcon name="arrow-right" size={14} className="dbis-section-fold__chevron ml-auto text-[var(--nasa-muted)]" />
|
||||
</summary>
|
||||
<div className="dbis-section-fold__body">{children}</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
128
frontend-dapp/src/features/hub/EcosystemLinkGrid.tsx
Normal file
128
frontend-dapp/src/features/hub/EcosystemLinkGrid.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import NasaIcon, { type NasaIconName } from '../../components/icons/NasaIcon';
|
||||
import type { EcosystemLink, EcosystemLinkAccent } from '../../config/ecosystemLinks';
|
||||
import { ecosystemLinkUsesAnchor } from '../../config/ecosystemLinkRouting';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
links: EcosystemLink[];
|
||||
columns?: '2' | '3';
|
||||
centered?: boolean;
|
||||
sectionIcon?: NasaIconName;
|
||||
variant?: 'grid' | 'list';
|
||||
hideHeader?: boolean;
|
||||
};
|
||||
|
||||
const ACCENT_STYLES: Record<EcosystemLinkAccent, { tile: string; icon: string }> = {
|
||||
green: { tile: 'bg-green-500/15 border-green-500/30 shadow-[0_0_20px_rgba(34,197,94,0.1)]', icon: 'text-green-400' },
|
||||
teal: { tile: 'bg-teal-500/15 border-teal-500/30 shadow-[0_0_20px_rgba(20,184,166,0.1)]', icon: 'text-teal-400' },
|
||||
blue: { tile: 'bg-sky-500/15 border-sky-500/30 shadow-[0_0_20px_rgba(56,189,248,0.1)]', icon: 'text-sky-400' },
|
||||
gold: { tile: 'bg-amber-500/15 border-amber-500/30 shadow-[0_0_20px_rgba(245,158,11,0.1)]', icon: 'text-amber-400' },
|
||||
purple: { tile: 'bg-violet-500/15 border-violet-500/30 shadow-[0_0_20px_rgba(139,92,246,0.1)]', icon: 'text-violet-400' },
|
||||
slate: { tile: 'bg-slate-500/15 border-slate-500/30', icon: 'text-slate-300' },
|
||||
};
|
||||
|
||||
function LinkCard({ link, variant }: { link: EcosystemLink; variant: 'grid' | 'list' }) {
|
||||
const accent = link.accent ?? 'green';
|
||||
const styles = ACCENT_STYLES[accent];
|
||||
const isExternal = link.external || link.href.startsWith('http');
|
||||
const useAnchor = ecosystemLinkUsesAnchor(link);
|
||||
const isList = variant === 'list';
|
||||
const className = [
|
||||
'hub-link-card group flex no-underline text-inherit rounded-xl border border-white/10 bg-[var(--nasa-panel)]',
|
||||
'hover:border-green-500/40 hover:bg-[var(--nasa-panel-elevated)] active:scale-[0.98] transition-all duration-150 shadow-sm',
|
||||
isList
|
||||
? 'hub-link-card--list flex-row items-center text-left gap-3 p-3 min-h-[60px]'
|
||||
: 'flex-col items-center text-center gap-3 p-4 min-h-[148px] hover:-translate-y-0.5',
|
||||
].join(' ');
|
||||
|
||||
const inner = (
|
||||
<>
|
||||
<span
|
||||
className={`hub-link-card__icon inline-flex items-center justify-center shrink-0 rounded-2xl border ${styles.tile} ${styles.icon} ${
|
||||
isList ? 'w-10 h-10' : 'w-14 h-14'
|
||||
}`}
|
||||
>
|
||||
<NasaIcon name={link.icon} size={isList ? 20 : 26} />
|
||||
</span>
|
||||
<div className={`hub-link-card__text ${isList ? 'flex-1 min-w-0' : 'flex-1 w-full'}`}>
|
||||
<div className={`flex items-center gap-1.5 ${isList ? '' : 'justify-center'}`}>
|
||||
<h3 className="text-sm font-semibold text-[var(--nasa-text)]">{link.label}</h3>
|
||||
{isExternal && <NasaIcon name="globe" size={11} className="text-[var(--nasa-muted)] opacity-70 shrink-0" />}
|
||||
</div>
|
||||
<p className={`text-xs text-[var(--nasa-muted)] leading-relaxed ${isList ? 'mt-0.5 truncate' : 'mt-1.5 line-clamp-2'}`}>
|
||||
{link.description}
|
||||
</p>
|
||||
</div>
|
||||
{!isList && (
|
||||
<span className="hub-link-card__open inline-flex items-center gap-1 text-xs font-medium text-green-500/80 group-hover:text-green-400">
|
||||
Open
|
||||
<NasaIcon name="arrow-right" size={12} />
|
||||
</span>
|
||||
)}
|
||||
{isList && <NasaIcon name="arrow-right" size={14} className="text-[var(--nasa-muted)] shrink-0" />}
|
||||
</>
|
||||
);
|
||||
|
||||
if (isExternal) {
|
||||
return (
|
||||
<a href={link.href} target="_blank" rel="noopener noreferrer" className={className}>
|
||||
{inner}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (useAnchor) {
|
||||
return (
|
||||
<a href={link.href} className={className}>
|
||||
{inner}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={link.href} className={className}>
|
||||
{inner}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EcosystemLinkGrid({
|
||||
title,
|
||||
subtitle,
|
||||
links,
|
||||
columns = '3',
|
||||
centered = true,
|
||||
sectionIcon = 'link',
|
||||
variant = 'grid',
|
||||
hideHeader = false,
|
||||
}: Props) {
|
||||
const gridClass =
|
||||
variant === 'list'
|
||||
? 'grid grid-cols-1 gap-2'
|
||||
: columns === '2'
|
||||
? 'grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4'
|
||||
: 'grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4';
|
||||
|
||||
return (
|
||||
<section className="mb-6 md:mb-10">
|
||||
{!hideHeader && (
|
||||
<div className={`mb-4 md:mb-5 hidden md:block${centered ? ' text-center' : ''}`}>
|
||||
<h2 className="dash-section-title flex items-center justify-center gap-2 text-base">
|
||||
<span className="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-green-500/10 border border-green-500/25 text-green-400">
|
||||
<NasaIcon name={sectionIcon} size={16} />
|
||||
</span>
|
||||
{title}
|
||||
</h2>
|
||||
{subtitle && <p className="dash-section-subtitle mt-1">{subtitle}</p>}
|
||||
</div>
|
||||
)}
|
||||
<div className={gridClass}>
|
||||
{links.map((link) => (
|
||||
<LinkCard key={link.id} link={link} variant={variant} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
56
frontend-dapp/src/features/hub/FeaturedAppsStrip.tsx
Normal file
56
frontend-dapp/src/features/hub/FeaturedAppsStrip.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import NasaIcon from '../../components/icons/NasaIcon';
|
||||
import { FEATURED_APP_LINKS, type EcosystemLink, type EcosystemLinkAccent } from '../../config/ecosystemLinks';
|
||||
import { ecosystemLinkUsesAnchor } from '../../config/ecosystemLinkRouting';
|
||||
|
||||
const ACCENT_TILE: Record<EcosystemLinkAccent, string> = {
|
||||
green: 'bg-green-500/15 border-green-500/30 text-green-400',
|
||||
teal: 'bg-teal-500/15 border-teal-500/30 text-teal-400',
|
||||
blue: 'bg-sky-500/15 border-sky-500/30 text-sky-400',
|
||||
gold: 'bg-amber-500/15 border-amber-500/30 text-amber-400',
|
||||
purple: 'bg-violet-500/15 border-violet-500/30 text-violet-400',
|
||||
slate: 'bg-slate-500/15 border-slate-500/30 text-slate-300',
|
||||
};
|
||||
|
||||
function FeaturedItem({ link }: { link: EcosystemLink }) {
|
||||
const accent = link.accent ?? 'green';
|
||||
const tile = ACCENT_TILE[accent];
|
||||
const short = link.label.replace('DBIS ', '').replace('Digital ', '');
|
||||
|
||||
const className = 'dbis-featured__item';
|
||||
const inner = (
|
||||
<>
|
||||
<span className={`dbis-featured__icon border ${tile}`}>
|
||||
<NasaIcon name={link.icon} size={22} />
|
||||
</span>
|
||||
<span className="dbis-featured__name">{short}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
if (ecosystemLinkUsesAnchor(link)) {
|
||||
return (
|
||||
<a href={link.href} className={className}>
|
||||
{inner}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={link.href} className={className}>
|
||||
{inner}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FeaturedAppsStrip() {
|
||||
return (
|
||||
<section className="dbis-featured" aria-label="Quick access">
|
||||
<p className="dbis-featured__label">Quick access</p>
|
||||
<div className="dbis-featured__scroll">
|
||||
{FEATURED_APP_LINKS.map((link) => (
|
||||
<FeaturedItem key={link.id} link={link} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
116
frontend-dapp/src/features/office24/Office24BankCorridors.tsx
Normal file
116
frontend-dapp/src/features/office24/Office24BankCorridors.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { fetchOmnlChainRegistry, type ChainRegistryResponse } from '../../config/supported-chains';
|
||||
import NasaIcon, { StatusIcon } from '../../components/icons/NasaIcon';
|
||||
import DashboardSection from '../omnl-dashboard/DashboardSection';
|
||||
|
||||
/** Office 24 Central Bank — 128-bank corridor status (settlement + SWIFT + M2 rails). */
|
||||
export default function Office24BankCorridors() {
|
||||
const [registry, setRegistry] = useState<ChainRegistryResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetchOmnlChainRegistry()
|
||||
.then(setRegistry)
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const chains = registry?.chains ?? [];
|
||||
const active = chains.filter((c) => c.status === 'active');
|
||||
const openCorridors = active.length;
|
||||
|
||||
return (
|
||||
<DashboardSection
|
||||
title="128-bank corridor network"
|
||||
subtitle="Open settlement lines · SWIFT · HYBX · M2 load · bridge · swap per chain"
|
||||
icon="globe"
|
||||
className="mb-8 lg:col-span-3"
|
||||
action={
|
||||
<span className="nasa-badge nasa-badge--nominal">
|
||||
{loading ? 'Loading…' : `${openCorridors} / ${registry?.maxCapacity ?? 128} corridors open`}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4">
|
||||
<div className="rounded border border-green-500/30 bg-green-500/5 p-3 text-center">
|
||||
<p className="text-2xl font-bold text-[#3dff8a]">{registry?.stats?.active ?? '—'}</p>
|
||||
<p className="text-xs text-[#7a9bb8]">Active chains</p>
|
||||
</div>
|
||||
<div className="rounded border border-white/10 p-3 text-center">
|
||||
<p className="text-2xl font-bold text-[#e8f4ff]">{registry?.primaryChainId ?? 138}</p>
|
||||
<p className="text-xs text-[#7a9bb8]">Settlement hub</p>
|
||||
</div>
|
||||
<div className="rounded border border-white/10 p-3 text-center">
|
||||
<p className="text-2xl font-bold text-[#e8f4ff]">{registry?.mirrorChainId ?? 651940}</p>
|
||||
<p className="text-xs text-[#7a9bb8]">Mirror chain</p>
|
||||
</div>
|
||||
<div className="rounded border border-white/10 p-3 text-center">
|
||||
<p className="text-2xl font-bold text-[#ffb347]">{registry?.stats?.staged ?? '—'}</p>
|
||||
<p className="text-xs text-[#7a9bb8]">Staged</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-[#7a9bb8] mb-3 flex items-center gap-2">
|
||||
<NasaIcon name="rail" size={12} />
|
||||
All active chains have full OMNL integration: Fineract posting, SWIFT MT102/MT103, trade finance (SBLC/BG/LC), M2
|
||||
token load, and bridge/swap when enabled in registry.
|
||||
</p>
|
||||
|
||||
<div className="overflow-x-auto max-h-[280px] overflow-y-auto rounded border border-white/10">
|
||||
<table className="w-full text-sm nasa-table">
|
||||
<thead className="sticky top-0">
|
||||
<tr className="text-left">
|
||||
<th className="py-2 px-2">Chain</th>
|
||||
<th className="py-2 px-2">Network</th>
|
||||
<th className="py-2 px-2">Corridor</th>
|
||||
<th className="py-2 px-2">SWIFT</th>
|
||||
<th className="py-2 px-2">Settlement</th>
|
||||
<th className="py-2 px-2">M2</th>
|
||||
<th className="py-2 px-2">Swap</th>
|
||||
<th className="py-2 px-2">Bridge</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{chains.slice(0, 128).map((c) => {
|
||||
const open = c.status === 'active';
|
||||
return (
|
||||
<tr key={c.chainId}>
|
||||
<td className="py-1.5 px-2 font-mono text-[#4ade80]">{c.chainId}</td>
|
||||
<td className="py-1.5 px-2 text-[#e8f4ff]">{c.name}</td>
|
||||
<td className="py-1.5 px-2">
|
||||
<span
|
||||
className={`text-xs font-mono uppercase ${open ? 'text-[#3dff8a]' : 'text-[#7a9bb8]'}`}
|
||||
>
|
||||
{open ? 'OPEN' : c.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-1.5 px-2">
|
||||
<StatusIcon ok={open} />
|
||||
</td>
|
||||
<td className="py-1.5 px-2">
|
||||
<StatusIcon ok={open} />
|
||||
</td>
|
||||
<td className="py-1.5 px-2">
|
||||
<StatusIcon ok={open && !!c.settlement?.m2LoadEnabled} />
|
||||
</td>
|
||||
<td className="py-1.5 px-2">
|
||||
<StatusIcon ok={!!c.settlement?.swapEnabled} />
|
||||
</td>
|
||||
<td className="py-1.5 px-2">
|
||||
<StatusIcon ok={!!c.settlement?.bridgeEnabled} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{!chains.length && !loading && (
|
||||
<tr>
|
||||
<td colSpan={8} className="py-6 text-center text-[#7a9bb8]">
|
||||
Chain registry unavailable — settlement API /chains
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DashboardSection>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FEATURED_APP_LINKS, IN_APP_LINKS, OPERATOR_CONSOLE_LINKS, PUBLIC_PORTAL_LINKS } from '../../config/ecosystemLinks';
|
||||
import { ecosystemLinkUsesAnchor } from '../../config/ecosystemLinkRouting';
|
||||
import NasaIcon, { StatusIcon } from '../../components/icons/NasaIcon';
|
||||
import DashboardSection from '../omnl-dashboard/DashboardSection';
|
||||
|
||||
const SUBJECT_GROUPS = [
|
||||
{ title: 'Core banking apps', links: FEATURED_APP_LINKS },
|
||||
{ title: 'Ecosystem subjects', links: IN_APP_LINKS.filter((l) => !FEATURED_APP_LINKS.some((f) => f.id === l.id)) },
|
||||
{ title: 'Operator consoles', links: OPERATOR_CONSOLE_LINKS },
|
||||
{ title: 'Production portals', links: PUBLIC_PORTAL_LINKS },
|
||||
];
|
||||
|
||||
export default function Office24EcosystemSubjects() {
|
||||
return (
|
||||
<DashboardSection
|
||||
title="Ecosystem integration"
|
||||
subtitle="All DBIS OMNL subjects · live corridors from Office 24 Central Bank"
|
||||
icon="network"
|
||||
className="mb-8"
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{SUBJECT_GROUPS.map((group) => (
|
||||
<div key={group.title}>
|
||||
<h3 className="text-sm font-semibold text-[#e8f4ff] mb-3 flex items-center gap-2">
|
||||
<NasaIcon name="check" size={12} className="nasa-icon--telemetry" />
|
||||
{group.title}
|
||||
</h3>
|
||||
<ul className="space-y-2">
|
||||
{group.links.map((link) => (
|
||||
<li key={link.id}>
|
||||
{ecosystemLinkUsesAnchor(link) ? (
|
||||
<a
|
||||
href={link.href}
|
||||
target={link.external ? '_blank' : undefined}
|
||||
rel={link.external ? 'noreferrer' : undefined}
|
||||
className="flex items-center justify-between gap-2 rounded border border-white/10 px-3 py-2 hover:border-green-500/40 text-sm"
|
||||
>
|
||||
<span className="flex items-center gap-2 text-[#e8f4ff]">
|
||||
<NasaIcon name={link.icon} size={14} />
|
||||
{link.label}
|
||||
</span>
|
||||
<StatusIcon ok />
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
to={link.href}
|
||||
className="flex items-center justify-between gap-2 rounded border border-white/10 px-3 py-2 hover:border-green-500/40 text-sm"
|
||||
>
|
||||
<span className="flex items-center gap-2 text-[#e8f4ff]">
|
||||
<NasaIcon name={link.icon} size={14} />
|
||||
{link.label}
|
||||
</span>
|
||||
<StatusIcon ok />
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DashboardSection>
|
||||
);
|
||||
}
|
||||
498
frontend-dapp/src/styles/app-dashboard.css
Normal file
498
frontend-dapp/src/styles/app-dashboard.css
Normal file
@@ -0,0 +1,498 @@
|
||||
/**
|
||||
* App dashboard — standard dark UI (replaces NASA mission-control styling).
|
||||
* Class names kept for compatibility; visuals use normal Inter typography and rounded cards.
|
||||
*/
|
||||
|
||||
.omnl-app,
|
||||
.app-shell,
|
||||
.nasa-mission-root {
|
||||
--nasa-bg: #141a16;
|
||||
--nasa-bg-deep: #0f1412;
|
||||
--nasa-panel: #1e2329;
|
||||
--nasa-panel-elevated: #252830;
|
||||
--nasa-panel-border: rgba(255, 255, 255, 0.1);
|
||||
--nasa-telemetry: #22c55e;
|
||||
--nasa-nominal: #22c55e;
|
||||
--nasa-caution: #f59e0b;
|
||||
--nasa-critical: #ef4444;
|
||||
--nasa-text: #eaecef;
|
||||
--nasa-muted: #848e9c;
|
||||
--nasa-mono: Inter, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
html:has(.omnl-app),
|
||||
html:has(.omnl-app) body {
|
||||
background: var(--nasa-bg);
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.omnl-app {
|
||||
background: var(--nasa-bg);
|
||||
color: var(--nasa-text);
|
||||
}
|
||||
|
||||
.nasa-icon--telemetry { color: var(--nasa-telemetry); }
|
||||
.nasa-icon--nominal { color: var(--nasa-nominal); }
|
||||
.nasa-icon--critical { color: var(--nasa-critical); }
|
||||
.nasa-icon--caution { color: var(--nasa-caution); }
|
||||
.nasa-icon--spin { animation: nasa-spin 1s linear infinite; }
|
||||
|
||||
@keyframes nasa-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.dash-page, .o24-page, .nasa-page, .cb-page {
|
||||
min-height: calc(100vh - 56px);
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.nasa-panel, .dash-card, .o24-card, .cb-card {
|
||||
background: var(--nasa-panel);
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.nasa-header {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0 0 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--nasa-panel-border);
|
||||
}
|
||||
|
||||
.nasa-header__meta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nasa-header__body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.nasa-header--centered .nasa-header__body {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nasa-header--centered .nasa-header__title-row {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nasa-header--centered .dash-page-subtitle {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.app-page-shell .dash-section-subtitle,
|
||||
.hub-page .dash-section-subtitle {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.app-page-shell {
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.app-page-shell .dash-card,
|
||||
.hub-page .dash-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nasa-header__title-row {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.nasa-header__icon-wrap {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
background: var(--nasa-panel-elevated);
|
||||
color: var(--nasa-telemetry);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nasa-title, .dash-page-title {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
text-transform: none;
|
||||
color: var(--nasa-text);
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.dash-page-subtitle {
|
||||
margin-top: 0.35rem;
|
||||
color: var(--nasa-muted);
|
||||
font-size: 0.95rem;
|
||||
max-width: 48rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.nasa-badge, .dash-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.25rem 0.65rem;
|
||||
border-radius: 6px;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.nasa-badge--telemetry, .dash-badge--blue {
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: #4ade80;
|
||||
border: 1px solid rgba(34, 197, 94, 0.25);
|
||||
}
|
||||
|
||||
.nasa-badge--nominal, .dash-badge--green {
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: #4ade80;
|
||||
border: 1px solid rgba(34, 197, 94, 0.25);
|
||||
}
|
||||
|
||||
.nasa-badge--caution, .dash-badge--gold {
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
color: #fbbf24;
|
||||
border: 1px solid rgba(245, 158, 11, 0.25);
|
||||
}
|
||||
|
||||
.dash-banner {
|
||||
border-radius: 10px;
|
||||
padding: 1rem 1.15rem;
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.dash-banner__title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.dash-banner__detail {
|
||||
font-size: 0.875rem;
|
||||
color: var(--nasa-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dash-banner--ok {
|
||||
background: rgba(34, 197, 94, 0.08);
|
||||
border-color: rgba(34, 197, 94, 0.25);
|
||||
}
|
||||
.dash-banner--ok .dash-banner__title { color: #4ade80; }
|
||||
|
||||
.dash-banner--warn {
|
||||
background: rgba(245, 158, 11, 0.08);
|
||||
border-color: rgba(245, 158, 11, 0.25);
|
||||
}
|
||||
.dash-banner--warn .dash-banner__title { color: #fbbf24; }
|
||||
|
||||
.dash-banner--error {
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
border-color: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
.dash-banner--error .dash-banner__title { color: #f87171; }
|
||||
|
||||
.dash-banner--loading {
|
||||
background: var(--nasa-panel);
|
||||
}
|
||||
|
||||
.dash-stat-label {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--nasa-muted);
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
margin-bottom: 0.35rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dash-stat-value {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--nasa-text);
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dash-stat-hint {
|
||||
margin-top: 0.35rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--nasa-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dash-section-title {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
color: var(--nasa-text);
|
||||
}
|
||||
|
||||
.dash-section-subtitle {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--nasa-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dash-kv {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
font-size: 0.875rem;
|
||||
border-bottom: 1px solid var(--nasa-panel-border);
|
||||
}
|
||||
|
||||
.dash-kv:last-child { border-bottom: none; }
|
||||
.dash-kv dt { color: var(--nasa-muted); flex-shrink: 0; }
|
||||
.dash-kv dd { color: var(--nasa-text); text-align: right; word-break: break-word; }
|
||||
.dash-kv .dash-kv-value--ok { color: #4ade80; font-weight: 500; }
|
||||
|
||||
.dash-quick-links__label {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
color: var(--nasa-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.dash-quick-links__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.4rem 0.85rem;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: normal;
|
||||
color: var(--nasa-text);
|
||||
background: var(--nasa-panel-elevated);
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
border-radius: 999px;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.dash-quick-links__chip:hover {
|
||||
border-color: var(--nasa-telemetry);
|
||||
color: var(--nasa-telemetry);
|
||||
background: rgba(34, 197, 94, 0.08);
|
||||
}
|
||||
|
||||
.dash-btn, .nasa-btn {
|
||||
padding: 0.55rem 1rem;
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
border-radius: 8px;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
background: var(--nasa-panel-elevated);
|
||||
color: var(--nasa-text);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.dash-btn:hover, .nasa-btn:hover {
|
||||
border-color: rgba(34, 197, 94, 0.4);
|
||||
background: rgba(34, 197, 94, 0.08);
|
||||
}
|
||||
|
||||
.nasa-btn--primary {
|
||||
background: #16a34a;
|
||||
border-color: #16a34a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nasa-btn--primary:hover {
|
||||
background: #22c55e;
|
||||
border-color: #22c55e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dash-btn:disabled, .nasa-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.nasa-input, .nasa-select {
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--nasa-panel-elevated);
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
border-radius: 8px;
|
||||
color: var(--nasa-text);
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.nasa-input:focus, .nasa-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--nasa-telemetry);
|
||||
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
|
||||
}
|
||||
|
||||
.nasa-inset-panel {
|
||||
background: var(--nasa-panel-elevated);
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.omnl-app-header {
|
||||
background: #1a241c !important;
|
||||
border-color: rgba(34, 197, 94, 0.2) !important;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.nasa-nav-brand {
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
color: var(--nasa-text) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nasa-nav-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
color: var(--nasa-muted);
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.nasa-nav-link:hover {
|
||||
color: var(--nasa-text);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.nasa-nav-link--active {
|
||||
color: #fff !important;
|
||||
background: #16a34a !important;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.nasa-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.nasa-table thead {
|
||||
background: var(--nasa-panel-elevated);
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
color: var(--nasa-muted);
|
||||
}
|
||||
|
||||
.nasa-table tbody tr { border-top: 1px solid var(--nasa-panel-border); }
|
||||
.nasa-table tbody tr:hover { background: rgba(255, 255, 255, 0.03); }
|
||||
|
||||
.trade-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
min-height: calc(100vh - 56px);
|
||||
background: var(--nasa-bg);
|
||||
}
|
||||
|
||||
.trade-markets, .trade-panel, .trade-pair-header, .trade-recent, .trade-book {
|
||||
background: var(--nasa-panel) !important;
|
||||
border-color: var(--nasa-panel-border) !important;
|
||||
}
|
||||
|
||||
.trade-center {
|
||||
min-height: 0;
|
||||
background: var(--nasa-bg);
|
||||
}
|
||||
|
||||
.trade-chart {
|
||||
background: var(--nasa-panel) !important;
|
||||
border-color: var(--nasa-panel-border) !important;
|
||||
}
|
||||
|
||||
.trade-chart__canvas {
|
||||
min-height: 240px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--nasa-panel-border);
|
||||
background: var(--nasa-panel-elevated);
|
||||
}
|
||||
|
||||
.trade-market-row:hover { background: rgba(255, 255, 255, 0.04); }
|
||||
.trade-market-row--active {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border-left: 2px solid var(--nasa-telemetry) !important;
|
||||
}
|
||||
|
||||
.trade-orderbook { max-height: 360px; overflow-y: auto; font-family: ui-monospace, monospace; font-size: 0.8rem; }
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.trade-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100dvh - 56px);
|
||||
}
|
||||
|
||||
.trade-center { order: 2; }
|
||||
|
||||
.trade-panel {
|
||||
order: 3;
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
border-left: none !important;
|
||||
border-top: 1px solid var(--nasa-panel-border);
|
||||
}
|
||||
|
||||
.trade-markets { display: none !important; }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.trade-layout { grid-template-columns: 240px 1fr 320px; }
|
||||
}
|
||||
369
frontend-dapp/src/styles/dbis-dashboard.css
Normal file
369
frontend-dapp/src/styles/dbis-dashboard.css
Normal file
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* DBIS Dashboard — mobile-first hub styling
|
||||
*/
|
||||
|
||||
.dbis-dashboard {
|
||||
background:
|
||||
radial-gradient(ellipse 120% 80% at 50% -20%, rgba(34, 197, 94, 0.12), transparent 55%),
|
||||
var(--nasa-bg, #141a16);
|
||||
}
|
||||
|
||||
.dbis-dashboard__inner {
|
||||
width: 100%;
|
||||
max-width: 56rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 0.75rem 5.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.dbis-dashboard__inner {
|
||||
padding: 1.5rem 1rem 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dbis-dashboard__hero {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.dbis-dashboard__hero {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dbis-dashboard__logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: 1rem;
|
||||
background: linear-gradient(145deg, #16a34a, #065f46);
|
||||
color: #fff;
|
||||
border: 1px solid rgba(74, 222, 128, 0.35);
|
||||
box-shadow: 0 8px 32px rgba(22, 163, 74, 0.35);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.dbis-dashboard__logo {
|
||||
width: 4.5rem;
|
||||
height: 4.5rem;
|
||||
border-radius: 1.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dbis-dashboard__eyebrow {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: #22c55e;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.dbis-dashboard__title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--nasa-text, #eaecef);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.dbis-dashboard__title {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dbis-dashboard__subtitle {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--nasa-muted, #848e9c);
|
||||
max-width: 28rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dbis-status-pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.dbis-status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.35rem 0.65rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
border: 1px solid rgba(34, 197, 94, 0.25);
|
||||
background: rgba(34, 197, 94, 0.08);
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
/* Featured horizontal strip */
|
||||
.dbis-featured {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.dbis-featured__label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--nasa-muted);
|
||||
margin-bottom: 0.65rem;
|
||||
padding-left: 0.15rem;
|
||||
}
|
||||
|
||||
.dbis-featured__scroll {
|
||||
display: flex;
|
||||
gap: 0.65rem;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0.35rem;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-snap-type: x mandatory;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.dbis-featured__scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dbis-featured__item {
|
||||
flex: 0 0 auto;
|
||||
scroll-snap-align: start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 5.25rem;
|
||||
max-width: 5.5rem;
|
||||
padding: 0.75rem 0.5rem;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--nasa-panel);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: border-color 0.2s, transform 0.2s, background 0.2s;
|
||||
min-height: 5.5rem;
|
||||
}
|
||||
|
||||
.dbis-featured__item:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.dbis-featured__item:hover {
|
||||
border-color: rgba(34, 197, 94, 0.4);
|
||||
background: var(--nasa-panel-elevated);
|
||||
}
|
||||
|
||||
.dbis-featured__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border-radius: 0.875rem;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.dbis-featured__name {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
color: var(--nasa-text);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dbis-featured__scroll {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
overflow: visible;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.dbis-featured__item {
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Collapsible sections on mobile */
|
||||
.dbis-section-fold {
|
||||
margin-bottom: 0.75rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 0.875rem;
|
||||
background: rgba(30, 35, 41, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dbis-section-fold__summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: 0.875rem 1rem;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
min-height: 3rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--nasa-text);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dbis-section-fold__summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dbis-section-fold__chevron {
|
||||
margin-left: auto;
|
||||
color: var(--nasa-muted);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.dbis-section-fold[open] .dbis-section-fold__chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.dbis-section-fold__body {
|
||||
padding: 0 0.75rem 0.875rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dbis-section-fold {
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dbis-section-fold__summary {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dbis-section-fold__body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Link cards */
|
||||
.hub-link-card {
|
||||
min-height: 8.5rem;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.hub-link-card {
|
||||
min-height: 7.5rem;
|
||||
padding: 0.875rem;
|
||||
}
|
||||
|
||||
.hub-link-card__icon {
|
||||
width: 3rem !important;
|
||||
height: 3rem !important;
|
||||
border-radius: 0.75rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.hub-link-card--list {
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
text-align: left !important;
|
||||
min-height: 3.75rem !important;
|
||||
padding: 0.75rem 1rem !important;
|
||||
gap: 0.75rem !important;
|
||||
}
|
||||
|
||||
.hub-link-card--list .hub-link-card__icon {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hub-link-card--list .hub-link-card__text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hub-link-card--list .hub-link-card__open {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hub-link-card--list h3 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hub-link-card--list p {
|
||||
text-align: left;
|
||||
margin-top: 0.15rem !important;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Mobile bottom nav */
|
||||
.dbis-bottom-nav {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: stretch;
|
||||
min-height: 3.5rem;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
background: rgba(26, 36, 28, 0.96);
|
||||
border-top: 1px solid rgba(34, 197, 94, 0.2);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dbis-bottom-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dbis-bottom-nav__item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.15rem;
|
||||
padding: 0.4rem 0.25rem;
|
||||
min-height: 3.5rem;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 500;
|
||||
color: #848e9c;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.dbis-bottom-nav__item--active {
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
.dbis-bottom-nav__item:active {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.dbis-has-bottom-nav {
|
||||
padding-bottom: calc(3.75rem + env(safe-area-inset-bottom, 0));
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.dbis-has-bottom-nav {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
13
frontend-dapp/src/styles/dbis-green-ui.css
Normal file
13
frontend-dapp/src/styles/dbis-green-ui.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@layer components {
|
||||
.ui-btn-primary {
|
||||
@apply bg-green-600 text-white font-medium rounded-xl hover:bg-green-500 transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-[#141a16] min-h-[44px] px-4 py-2;
|
||||
}
|
||||
|
||||
.ui-btn-secondary {
|
||||
@apply bg-[#1a241c] text-white font-medium rounded-xl border border-green-500/30 hover:bg-green-900/30 transition-colors min-h-[44px] px-4 py-2;
|
||||
}
|
||||
|
||||
.ui-btn-icon {
|
||||
@apply p-2 rounded-lg text-green-400 hover:bg-green-500/10 min-h-[44px] min-w-[44px];
|
||||
}
|
||||
}
|
||||
8
services/token-aggregation/public/omnl-api-base.js
Normal file
8
services/token-aggregation/public/omnl-api-base.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
/** auto → same-origin /api/v1 (vite proxy, portal-serve, or token-aggregation). */
|
||||
window.resolveOmnlApiBase = function () {
|
||||
const meta = document.querySelector('meta[name="omnl-api-base"]')?.getAttribute('content')?.trim();
|
||||
if (meta && meta !== 'auto') return meta.replace(/\/$/, '');
|
||||
return '/api/v1';
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user