refactor: rename SolaceScanScout to Solace and update related configurations

- Updated branding from "SolaceScanScout" to "Solace" across various files including deployment scripts, API responses, and documentation.
- Changed default base URL for Playwright tests and updated security headers to reflect the new branding.
- Enhanced README and API documentation to include new authentication endpoints and product access details.

This refactor aligns the project branding and improves clarity in the API documentation.
This commit is contained in:
defiQUG
2026-04-10 12:52:17 -07:00
parent bdae5a9f6e
commit f46bd213ba
160 changed files with 13274 additions and 1061 deletions

View File

@@ -1,44 +1,96 @@
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { useState } from 'react'
import { usePathname, useRouter } from 'next/navigation'
import { useEffect, useId, useRef, useState } from 'react'
import { accessApi, type WalletAccessSession } from '@/services/api/access'
const navLink = 'text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors'
const navLinkActive = 'text-primary-600 dark:text-primary-400 font-medium'
const navItemBase =
'rounded-xl px-3 py-2 text-[15px] font-medium transition-all duration-150'
const navLink =
`${navItemBase} text-gray-700 dark:text-gray-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:bg-gray-700/70 dark:hover:text-primary-300`
const navLinkActive =
`${navItemBase} bg-primary-50 text-primary-700 shadow-sm ring-1 ring-primary-100 dark:bg-primary-500/10 dark:text-primary-300 dark:ring-primary-500/20`
function NavDropdown({
label,
icon,
active,
children,
}: {
label: string
icon: React.ReactNode
active?: boolean
children: React.ReactNode
}) {
const [open, setOpen] = useState(false)
const wrapperRef = useRef<HTMLDivElement | null>(null)
const menuId = useId()
useEffect(() => {
if (!open) return
const handlePointerDown = (event: MouseEvent | TouchEvent) => {
const target = event.target as Node | null
if (!target || !wrapperRef.current?.contains(target)) {
setOpen(false)
}
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setOpen(false)
}
}
document.addEventListener('mousedown', handlePointerDown)
document.addEventListener('touchstart', handlePointerDown)
document.addEventListener('keydown', handleKeyDown)
return () => {
document.removeEventListener('mousedown', handlePointerDown)
document.removeEventListener('touchstart', handlePointerDown)
document.removeEventListener('keydown', handleKeyDown)
}
}, [open])
return (
<div
ref={wrapperRef}
className="relative"
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onBlurCapture={(event) => {
const nextTarget = event.relatedTarget as Node | null
if (nextTarget && wrapperRef.current?.contains(nextTarget)) {
return
}
setOpen(false)
}}
>
<button
type="button"
className={`flex items-center gap-1.5 px-3 py-2 rounded-md ${navLink}`}
onClick={() => setOpen((v) => !v)}
className={`flex items-center gap-1.5 ${active && !open ? navLinkActive : navLink}`}
onClick={() => setOpen((value) => !value)}
onKeyDown={(event) => {
if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
setOpen(true)
}
}}
aria-expanded={open}
aria-haspopup="true"
aria-controls={menuId}
>
{icon}
<span>{label}</span>
<svg className={`w-3.5 h-3.5 transition-transform ${open ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden>
<svg className={`h-3.5 w-3.5 transition-transform ${open ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{open && (
<ul
className="absolute left-0 top-full mt-1 min-w-[200px] rounded-lg bg-white dark:bg-gray-800 shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50"
id={menuId}
className="absolute left-0 top-full z-50 mt-1 min-w-[220px] rounded-lg border border-gray-200 bg-white py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800"
role="menu"
>
{children}
@@ -59,7 +111,8 @@ function DropdownItem({
children: React.ReactNode
external?: boolean
}) {
const className = `flex items-center gap-2 px-4 py-2.5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-primary-600 dark:hover:text-primary-400 ${navLink}`
const className =
'flex items-center gap-2 px-4 py-2.5 text-gray-700 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-primary-400'
if (external) {
return (
<li role="none">
@@ -81,30 +134,91 @@ function DropdownItem({
}
export default function Navbar() {
const router = useRouter()
const pathname = usePathname() ?? ''
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [exploreOpen, setExploreOpen] = useState(false)
const [toolsOpen, setToolsOpen] = useState(false)
const [dataOpen, setDataOpen] = useState(false)
const [operationsOpen, setOperationsOpen] = useState(false)
const [walletSession, setWalletSession] = useState<WalletAccessSession | null>(null)
const [connectingWallet, setConnectingWallet] = useState(false)
const isExploreActive =
pathname === '/' ||
pathname.startsWith('/blocks') ||
pathname.startsWith('/transactions') ||
pathname.startsWith('/addresses')
const isDataActive =
pathname.startsWith('/tokens') ||
pathname.startsWith('/pools') ||
pathname.startsWith('/analytics') ||
pathname.startsWith('/watchlist')
const isOperationsActive =
pathname.startsWith('/bridge') ||
pathname.startsWith('/routes') ||
pathname.startsWith('/liquidity') ||
pathname.startsWith('/operations') ||
pathname.startsWith('/operator') ||
pathname.startsWith('/system') ||
pathname.startsWith('/weth')
const isDocsActive = pathname.startsWith('/docs')
const isAccessActive = pathname.startsWith('/access')
useEffect(() => {
const syncWalletSession = () => {
setWalletSession(accessApi.getStoredWalletSession())
}
syncWalletSession()
window.addEventListener('storage', syncWalletSession)
window.addEventListener('explorer-access-session-changed', syncWalletSession)
return () => {
window.removeEventListener('storage', syncWalletSession)
window.removeEventListener('explorer-access-session-changed', syncWalletSession)
}
}, [])
const handleAccessClick = async () => {
if (walletSession) {
router.push('/access')
setMobileMenuOpen(false)
return
}
try {
setConnectingWallet(true)
await accessApi.connectWalletSession()
router.push('/access')
setMobileMenuOpen(false)
} catch (error) {
console.error('Wallet connect failed', error)
router.push('/access')
setMobileMenuOpen(false)
} finally {
setConnectingWallet(false)
}
}
const toggleMobileMenu = () => {
setMobileMenuOpen((open) => {
const nextOpen = !open
if (!nextOpen) {
setExploreOpen(false)
setToolsOpen(false)
setDataOpen(false)
setOperationsOpen(false)
}
return nextOpen
})
}
return (
<nav className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<nav className="border-b border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
<div className="container mx-auto px-4">
<div className="flex h-14 items-center justify-between sm:h-16">
<div className="flex min-w-0 items-center gap-3 md:gap-8">
<div className="flex min-w-0 items-center gap-3 md:gap-6">
<Link
href="/"
className="group inline-flex max-w-[calc(100vw-5rem)] flex-col rounded-xl px-2 py-1.5 text-base font-bold text-primary-600 transition-colors hover:bg-primary-50 dark:text-primary-400 dark:hover:bg-gray-700/70 sm:max-w-none sm:px-3 sm:py-2 sm:text-xl"
className="group inline-flex max-w-[calc(100vw-5rem)] flex-col rounded-xl px-2 py-1.5 text-base font-bold text-primary-600 transition-colors hover:bg-primary-50 dark:text-primary-400 dark:hover:bg-gray-700/70 sm:max-w-none sm:px-2.5 sm:py-1.5 sm:text-[1.05rem]"
onClick={() => setMobileMenuOpen(false)}
aria-label="Go to explorer home"
>
@@ -116,58 +230,87 @@ export default function Navbar() {
</span>
<span className="min-w-0 truncate">
<span className="sm:hidden">SolaceScan</span>
<span className="hidden sm:inline">SolaceScanScout</span>
<span className="hidden sm:inline">SolaceScan</span>
</span>
</span>
<span className="mt-0.5 hidden text-xs font-normal text-gray-500 transition-colors group-hover:text-gray-700 dark:text-gray-400 dark:group-hover:text-gray-200 sm:block">
The Defi Oracle Meta Explorer
<span className="mt-0.5 hidden text-[0.78rem] font-normal text-gray-500 transition-colors group-hover:text-gray-700 dark:text-gray-400 dark:group-hover:text-gray-200 sm:block">
Chain 138 Explorer by DBIS
</span>
</Link>
<div className="hidden md:flex items-center gap-1">
<div className="hidden items-center gap-1.5 md:flex">
<NavDropdown
label="Explore"
icon={<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0h.5a2.5 2.5 0 002.5-2.5V3.935M12 12a2 2 0 104 0 2 2 0 00-4 0z" /></svg>}
active={isExploreActive}
icon={<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0h.5a2.5 2.5 0 002.5-2.5V3.935M12 12a2 2 0 104 0 2 2 0 00-4 0z" /></svg>}
>
<DropdownItem href="/" icon={<span className="text-gray-400"></span>}>Home</DropdownItem>
<DropdownItem href="/blocks" icon={<span className="text-gray-400"></span>}>Blocks</DropdownItem>
<DropdownItem href="/transactions" icon={<span className="text-gray-400"></span>}>Transactions</DropdownItem>
<DropdownItem href="/addresses" icon={<span className="text-gray-400"></span>}>Addresses</DropdownItem>
</NavDropdown>
<Link
href="/search"
className={`hidden md:inline-flex items-center ${pathname.startsWith('/search') ? navLinkActive : navLink}`}
>
Search
</Link>
<NavDropdown
label="Data"
active={isDataActive}
icon={<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7h16M4 12h16M4 17h10" /></svg>}
>
<DropdownItem href="/tokens">Tokens</DropdownItem>
<DropdownItem href="/analytics">Analytics</DropdownItem>
<DropdownItem href="/pools">Pools</DropdownItem>
<DropdownItem href="/watchlist">Watchlist</DropdownItem>
</NavDropdown>
<Link
href="/docs"
className={`hidden md:inline-flex items-center ${isDocsActive ? navLinkActive : navLink}`}
>
Docs
</Link>
<NavDropdown
label="Operations"
active={isOperationsActive}
icon={<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>}
>
<DropdownItem href="/operations">Operations Hub</DropdownItem>
<DropdownItem href="/bridge">Bridge</DropdownItem>
<DropdownItem href="/routes">Routes</DropdownItem>
<DropdownItem href="/liquidity">Liquidity</DropdownItem>
<DropdownItem href="/system">System</DropdownItem>
<DropdownItem href="/operator">Operator</DropdownItem>
<DropdownItem href="/weth">WETH</DropdownItem>
<DropdownItem href="/chain138-command-center.html" external>Command Center</DropdownItem>
</NavDropdown>
<Link
href="/wallet"
className={`hidden md:inline-flex items-center px-3 py-2 rounded-md ${pathname.startsWith('/wallet') ? navLinkActive : navLink}`}
className={`hidden md:inline-flex items-center ${pathname.startsWith('/wallet') ? navLinkActive : navLink}`}
>
Wallet
</Link>
<NavDropdown
label="Tools"
icon={<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>}
<button
type="button"
onClick={() => void handleAccessClick()}
className={`hidden md:inline-flex items-center ${isAccessActive ? navLinkActive : navLink}`}
>
<DropdownItem href="/search">Search</DropdownItem>
<DropdownItem href="/tokens">Tokens</DropdownItem>
<DropdownItem href="/pools">Pools</DropdownItem>
<DropdownItem href="/watchlist">Watchlist</DropdownItem>
<DropdownItem href="/wallet">Wallet</DropdownItem>
<DropdownItem href="/liquidity">Liquidity</DropdownItem>
<DropdownItem href="/bridge">Bridge</DropdownItem>
<DropdownItem href="/routes">Routes</DropdownItem>
<DropdownItem href="/more">More</DropdownItem>
<DropdownItem href="/chain138-command-center.html" external>Command Center</DropdownItem>
</NavDropdown>
{connectingWallet ? 'Connecting…' : walletSession ? 'Access' : 'Connect Wallet'}
</button>
</div>
</div>
<div className="flex items-center md:hidden">
<button
type="button"
className="p-2 rounded-md text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
className="rounded-md p-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"
onClick={toggleMobileMenu}
aria-expanded={mobileMenuOpen}
aria-label="Toggle menu"
>
{mobileMenuOpen ? (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
) : (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /></svg>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /></svg>
)}
</button>
</div>
@@ -175,40 +318,62 @@ export default function Navbar() {
{mobileMenuOpen && (
<div className="border-t border-gray-200 py-2 pb-3 dark:border-gray-700 md:hidden">
<div className="flex flex-col gap-1">
<Link href="/" className={`px-3 py-2.5 rounded-md ${pathname === '/' ? navLinkActive : navLink}`} onClick={() => setMobileMenuOpen(false)}>Home</Link>
<Link href="/" className={pathname === '/' ? navLinkActive : navLink} onClick={() => setMobileMenuOpen(false)}>Home</Link>
<Link href="/search" className={pathname.startsWith('/search') ? navLinkActive : navLink} onClick={() => setMobileMenuOpen(false)}>Search</Link>
<div className="relative">
<button type="button" className={`flex items-center justify-between w-full px-3 py-2.5 rounded-md ${navLink}`} onClick={() => setExploreOpen((o) => !o)} aria-expanded={exploreOpen}>
<button type="button" className={`flex w-full items-center justify-between ${navLink}`} onClick={() => setExploreOpen((value) => !value)} aria-expanded={exploreOpen}>
<span>Explore</span>
<svg className={`w-4 h-4 transition-transform ${exploreOpen ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
<svg className={`h-4 w-4 transition-transform ${exploreOpen ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
</button>
{exploreOpen && (
<ul className="pl-4 mt-1 space-y-0.5">
<li><Link href="/blocks" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Blocks</Link></li>
<li><Link href="/transactions" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Transactions</Link></li>
<li><Link href="/addresses" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Addresses</Link></li>
<ul className="mt-1 space-y-0.5 pl-4">
<li><Link href="/blocks" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Blocks</Link></li>
<li><Link href="/transactions" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Transactions</Link></li>
<li><Link href="/addresses" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Addresses</Link></li>
</ul>
)}
</div>
<div className="relative">
<button type="button" className={`flex items-center justify-between w-full px-3 py-2.5 rounded-md ${navLink}`} onClick={() => setToolsOpen((o) => !o)} aria-expanded={toolsOpen}>
<span>Tools</span>
<svg className={`w-4 h-4 transition-transform ${toolsOpen ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
<button type="button" className={`flex w-full items-center justify-between ${navLink}`} onClick={() => setDataOpen((value) => !value)} aria-expanded={dataOpen}>
<span>Data</span>
<svg className={`h-4 w-4 transition-transform ${dataOpen ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
</button>
{toolsOpen && (
<ul className="pl-4 mt-1 space-y-0.5">
<li><Link href="/search" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Search</Link></li>
<li><Link href="/tokens" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Tokens</Link></li>
<li><Link href="/pools" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Pools</Link></li>
<li><Link href="/watchlist" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Watchlist</Link></li>
<li><Link href="/wallet" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Wallet</Link></li>
<li><Link href="/liquidity" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Liquidity</Link></li>
<li><Link href="/bridge" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Bridge</Link></li>
<li><Link href="/routes" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Routes</Link></li>
<li><Link href="/more" className={`block px-3 py-2 rounded-md ${navLink}`} onClick={() => setMobileMenuOpen(false)}>More</Link></li>
<li><a href="/chain138-command-center.html" className={`block px-3 py-2 rounded-md ${navLink}`} target="_blank" rel="noopener noreferrer" onClick={() => setMobileMenuOpen(false)}>Command Center</a></li>
{dataOpen && (
<ul className="mt-1 space-y-0.5 pl-4">
<li><Link href="/tokens" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Tokens</Link></li>
<li><Link href="/analytics" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Analytics</Link></li>
<li><Link href="/pools" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Pools</Link></li>
<li><Link href="/watchlist" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Watchlist</Link></li>
</ul>
)}
</div>
<Link href="/docs" className={isDocsActive ? navLinkActive : navLink} onClick={() => setMobileMenuOpen(false)}>Docs</Link>
<div className="relative">
<button type="button" className={`flex w-full items-center justify-between ${navLink}`} onClick={() => setOperationsOpen((value) => !value)} aria-expanded={operationsOpen}>
<span>Operations</span>
<svg className={`h-4 w-4 transition-transform ${operationsOpen ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
</button>
{operationsOpen && (
<ul className="mt-1 space-y-0.5 pl-4">
<li><Link href="/operations" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Operations Hub</Link></li>
<li><Link href="/bridge" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Bridge</Link></li>
<li><Link href="/routes" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Routes</Link></li>
<li><Link href="/liquidity" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Liquidity</Link></li>
<li><Link href="/system" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>System</Link></li>
<li><Link href="/operator" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>Operator</Link></li>
<li><Link href="/weth" className={`block rounded-md px-3 py-2 ${navLink}`} onClick={() => setMobileMenuOpen(false)}>WETH</Link></li>
<li><a href="/chain138-command-center.html" className={`block rounded-md px-3 py-2 ${navLink}`} target="_blank" rel="noopener noreferrer" onClick={() => setMobileMenuOpen(false)}>Command Center</a></li>
</ul>
)}
</div>
<Link href="/wallet" className={pathname.startsWith('/wallet') ? navLinkActive : navLink} onClick={() => setMobileMenuOpen(false)}>Wallet</Link>
<button
type="button"
className={`w-full text-left ${isAccessActive ? navLinkActive : navLink}`}
onClick={() => void handleAccessClick()}
>
{connectingWallet ? 'Connecting wallet…' : walletSession ? 'Access' : 'Connect wallet'}
</button>
</div>
</div>
)}