From efd7c8bbcbc2e4930cbbb87b9cba4dfb790f3e6d Mon Sep 17 00:00:00 2001 From: defiQUG Date: Fri, 22 May 2026 22:54:08 -0700 Subject: [PATCH] Complete UX audit P3: API copy URLs, labels, retry, and smoke sync. Add footer copy-to-clipboard for public APIs, align ops page labels, improve mobile brand lockup, surface WalletConnect posture on wallet tools, add account access discovery, liquidity retry alerts, and refresh smoke-route expectations. Co-authored-by: Cursor --- frontend/public/chain138-command-center.html | 4 +- frontend/scripts/smoke-routes.mjs | 13 +++-- .../src/components/common/BrandLockup.tsx | 2 +- .../components/common/ExplorerRetryAlert.tsx | 34 ++++++++++++ frontend/src/components/common/Footer.tsx | 13 +---- .../common/FooterPublicApiLinks.tsx | 53 +++++++++++++++++++ frontend/src/components/common/Navbar.tsx | 2 +- .../explorer/LiquidityOperationsPage.tsx | 17 ++++-- frontend/src/components/home/HomePage.tsx | 5 +- .../wallet/WalletConnectPostureNote.tsx | 32 +++++++++++ frontend/src/components/wallet/WalletPage.tsx | 6 ++- frontend/src/data/explorerOperations.ts | 24 ++++----- scripts/e2e-sprint-smoke.spec.ts | 15 +++++- 13 files changed, 179 insertions(+), 41 deletions(-) create mode 100644 frontend/src/components/common/ExplorerRetryAlert.tsx create mode 100644 frontend/src/components/common/FooterPublicApiLinks.tsx create mode 100644 frontend/src/components/wallet/WalletConnectPostureNote.tsx diff --git a/frontend/public/chain138-command-center.html b/frontend/public/chain138-command-center.html index 8a2fa2b..9307a25 100644 --- a/frontend/public/chain138-command-center.html +++ b/frontend/public/chain138-command-center.html @@ -152,8 +152,8 @@
If diagram rendering is unavailable, use the main explorer operational surfaces directly: - Operations Hub, - Bridge Monitoring, + Operations hub, + Bridge, Routes, System, and Operator. diff --git a/frontend/scripts/smoke-routes.mjs b/frontend/scripts/smoke-routes.mjs index cb2243d..f3e1aca 100644 --- a/frontend/scripts/smoke-routes.mjs +++ b/frontend/scripts/smoke-routes.mjs @@ -4,15 +4,15 @@ const baseUrl = (process.env.BASE_URL || 'https://explorer.d-bis.org').replace(/ const addressUnderTest = process.env.SMOKE_ADDRESS || '0x99b3511a2d315a497c8112c1fdd8d508d4b1e506' const checks = [ - { path: '/', expectTexts: ['DBIS Explorer', 'Recent Blocks', 'Open wallet tools'] }, + { path: '/', expectTexts: ['DBIS Explorer', 'Recent Blocks', 'Network overview'] }, { path: '/blocks', expectTexts: ['Blocks'] }, { path: '/transactions', expectTexts: ['Transactions'] }, { path: '/addresses', expectTexts: ['Addresses', 'Open An Address'] }, { path: '/watchlist', expectTexts: ['Watchlist', 'Saved Addresses'] }, { path: '/pools', expectTexts: ['Pools', 'Pool operation shortcuts'] }, - { path: '/liquidity', expectTexts: ['Chain 138 Liquidity Access', 'Explorer Access Points'] }, - { path: '/wallet', expectTexts: ['Wallet & MetaMask', 'Install Open Snap'] }, - { path: '/tokens', expectTexts: ['Tokens', 'Find A Token'] }, + { path: '/liquidity', expectTexts: ['Liquidity', 'Explorer Access Points'] }, + { path: '/wallet', expectTexts: ['Wallet Tools', 'WalletConnect v2 posture'] }, + { path: '/tokens', expectTexts: ['Tokens', 'Find a token'] }, { path: '/search', expectTexts: ['Search'], placeholder: 'Search by address, transaction hash, block number...' }, { path: `/addresses/${addressUnderTest}`, expectTexts: [], anyOfTexts: ['Back to addresses', 'Address not found'] }, ] @@ -22,7 +22,10 @@ async function bodyText(page) { } async function hasShell(page) { - const homeLink = await page.getByRole('link', { name: /Go to explorer home/i }).isVisible().catch(() => false) + const homeLink = await page + .getByRole('link', { name: /Go to DBIS Explorer home|Go to explorer home/i }) + .isVisible() + .catch(() => false) const supportText = await page.getByText(/Support:/i).isVisible().catch(() => false) return homeLink && supportText } diff --git a/frontend/src/components/common/BrandLockup.tsx b/frontend/src/components/common/BrandLockup.tsx index 593288e..c2a3a1d 100644 --- a/frontend/src/components/common/BrandLockup.tsx +++ b/frontend/src/components/common/BrandLockup.tsx @@ -15,7 +15,7 @@ export default function BrandLockup({ compact = false }: { compact?: boolean }) diff --git a/frontend/src/components/common/ExplorerRetryAlert.tsx b/frontend/src/components/common/ExplorerRetryAlert.tsx new file mode 100644 index 0000000..1ccf20f --- /dev/null +++ b/frontend/src/components/common/ExplorerRetryAlert.tsx @@ -0,0 +1,34 @@ +interface ExplorerRetryAlertProps { + message: string + onRetry?: () => void + retryLabel?: string + className?: string +} + +export default function ExplorerRetryAlert({ + message, + onRetry, + retryLabel = 'Retry', + className = '', +}: ExplorerRetryAlertProps) { + return ( +
+

{message}

+ {onRetry ? ( + + ) : null} +
+ ) +} diff --git a/frontend/src/components/common/Footer.tsx b/frontend/src/components/common/Footer.tsx index ad5870f..3364b44 100644 --- a/frontend/src/components/common/Footer.tsx +++ b/frontend/src/components/common/Footer.tsx @@ -1,5 +1,5 @@ import Link from 'next/link' -import { explorerPublicApiLinks } from '@/data/explorerOperations' +import FooterPublicApiLinks from '@/components/common/FooterPublicApiLinks' const footerLinkClass = 'text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors' @@ -62,16 +62,7 @@ export default function Footer() {
Public APIs
-
    - {explorerPublicApiLinks.map((link) => ( -
  • - - {link.label} - -

    {link.description}

    -
  • - ))} -
+

Read-only JSON endpoints on the public explorer domain. No API key required.

diff --git a/frontend/src/components/common/FooterPublicApiLinks.tsx b/frontend/src/components/common/FooterPublicApiLinks.tsx new file mode 100644 index 0000000..0b0cce8 --- /dev/null +++ b/frontend/src/components/common/FooterPublicApiLinks.tsx @@ -0,0 +1,53 @@ +'use client' + +import { useState } from 'react' +import { explorerPublicApiLinks } from '@/data/explorerOperations' + +const footerLinkClass = + 'text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors' + +function absoluteApiUrl(href: string): string { + if (typeof window === 'undefined') return href + if (href.startsWith('http://') || href.startsWith('https://')) return href + return `${window.location.origin}${href.startsWith('/') ? href : `/${href}`}` +} + +export default function FooterPublicApiLinks() { + const [copiedHref, setCopiedHref] = useState(null) + + const copyUrl = async (href: string) => { + if (typeof navigator === 'undefined' || !navigator.clipboard) return + try { + await navigator.clipboard.writeText(absoluteApiUrl(href)) + setCopiedHref(href) + window.setTimeout(() => setCopiedHref((current) => (current === href ? null : current)), 1500) + } catch { + setCopiedHref(null) + } + } + + return ( +
    + {explorerPublicApiLinks.map((link) => ( +
  • +
    +
    + + {link.label} + +

    {link.description}

    +
    + +
    +
  • + ))} +
+ ) +} diff --git a/frontend/src/components/common/Navbar.tsx b/frontend/src/components/common/Navbar.tsx index a26a6b9..aba5875 100644 --- a/frontend/src/components/common/Navbar.tsx +++ b/frontend/src/components/common/Navbar.tsx @@ -717,7 +717,7 @@ export default function Navbar() {
setMobileMenuOpen(false)} aria-label="Go to DBIS Explorer home" > diff --git a/frontend/src/components/explorer/LiquidityOperationsPage.tsx b/frontend/src/components/explorer/LiquidityOperationsPage.tsx index f5285dc..90774ed 100644 --- a/frontend/src/components/explorer/LiquidityOperationsPage.tsx +++ b/frontend/src/components/explorer/LiquidityOperationsPage.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react' import Link from 'next/link' import { Card } from '@/libs/frontend-ui-primitives' +import ExplorerRetryAlert from '@/components/common/ExplorerRetryAlert' import { type TokenListResponse } from '@/services/api/config' import { tokensApi } from '@/services/api/tokens' import { @@ -79,6 +80,7 @@ export default function LiquidityOperationsPage({ const [stats, setStats] = useState(initialStats) const [bridgeStatus, setBridgeStatus] = useState(initialBridgeStatus) const [loadingError, setLoadingError] = useState(null) + const [reloadKey, setReloadKey] = useState(0) const [copiedEndpoint, setCopiedEndpoint] = useState(null) useEffect(() => { @@ -99,6 +101,7 @@ export default function LiquidityOperationsPage({ } const load = async () => { + setLoadingError(null) const [tokenListResult, routeMatrixResult, plannerCapabilitiesResult, planResult, statsResult, bridgeResult] = await Promise.allSettled([ tokensApi.listForSurface('extended', 138).then(({ ok, data }) => ({ tokens: ok ? data : [] })), @@ -163,6 +166,7 @@ export default function LiquidityOperationsPage({ initialStats, initialTokenList, initialTokenPoolRecords, + reloadKey, ]) const featuredTokens = useMemo( @@ -266,7 +270,7 @@ export default function LiquidityOperationsPage({
- Chain 138 Liquidity Access + Liquidity

Public liquidity, route discovery, and execution access points @@ -282,9 +286,14 @@ export default function LiquidityOperationsPage({ {loadingError ? ( - -

{loadingError}

-
+ { + setLoadingError(null) + setReloadKey((value) => value + 1) + }} + /> ) : null}
diff --git a/frontend/src/components/home/HomePage.tsx b/frontend/src/components/home/HomePage.tsx index c449200..eb76a59 100644 --- a/frontend/src/components/home/HomePage.tsx +++ b/frontend/src/components/home/HomePage.tsx @@ -650,7 +650,7 @@ export default function Home({ href="/bridge" className="inline-flex items-center justify-center rounded-xl bg-gray-900 px-4 py-2.5 text-sm font-semibold text-white hover:bg-black dark:bg-white dark:text-gray-900 dark:hover:bg-gray-100" > - Open bridge monitoring + Open bridge Search + + Account access + Tokens diff --git a/frontend/src/components/wallet/WalletConnectPostureNote.tsx b/frontend/src/components/wallet/WalletConnectPostureNote.tsx new file mode 100644 index 0000000..1b4ca8e --- /dev/null +++ b/frontend/src/components/wallet/WalletConnectPostureNote.tsx @@ -0,0 +1,32 @@ +'use client' + +import { useEffect, useState } from 'react' +import { getWalletConnectConfig, type WalletConnectConfigResponse } from '@/services/api/walletConnect' + +export default function WalletConnectPostureNote() { + const [config, setConfig] = useState(null) + + useEffect(() => { + let cancelled = false + getWalletConnectConfig() + .then((value) => { + if (!cancelled) setConfig(value) + }) + .catch(() => { + if (!cancelled) setConfig(null) + }) + return () => { + cancelled = true + } + }, []) + + if (!config) return null + + return ( +

+ WalletConnect v2 posture: {config.enabled ? 'enabled' : config.status}. Browser extension wallets use{' '} + {config.fallbackAuth} today. + {config.message ? ` ${config.message}` : ''} +

+ ) +} diff --git a/frontend/src/components/wallet/WalletPage.tsx b/frontend/src/components/wallet/WalletPage.tsx index 79889e5..cc9f558 100644 --- a/frontend/src/components/wallet/WalletPage.tsx +++ b/frontend/src/components/wallet/WalletPage.tsx @@ -6,6 +6,7 @@ import type { TokenListCatalog, } from '@/components/wallet/AddToMetaMask' import { AddToMetaMask } from '@/components/wallet/AddToMetaMask' +import WalletConnectPostureNote from '@/components/wallet/WalletConnectPostureNote' import Link from 'next/link' import { Explain, useUiMode } from '@/components/common/UiModeContext' import { accessApi, type WalletAccessSession } from '@/services/api/access' @@ -192,6 +193,7 @@ export default function WalletPage(props: WalletPageProps) { ? 'Use the explorer-served network catalog, token list, and capability metadata to connect Chain 138 (DeFi Oracle Meta Mainnet) and Ethereum Mainnet to MetaMask and other Web3 wallets.' : 'Use explorer-served network and token metadata to connect Chain 138 and Ethereum Mainnet wallets.'}

+
@@ -483,7 +485,7 @@ export default function WalletPage(props: WalletPageProps) { <> Need swap and liquidity discovery too? Visit the{' '} - Liquidity Access + Liquidity {' '} page for live Chain 138 pools, route matrix links, partner payload templates, and the internal fallback execution plan endpoints. @@ -492,7 +494,7 @@ export default function WalletPage(props: WalletPageProps) { <> Liquidity and planner posture lives on the{' '} - Liquidity Access + Liquidity {' '} surface. diff --git a/frontend/src/data/explorerOperations.ts b/frontend/src/data/explorerOperations.ts index c1e470c..f7a56b0 100644 --- a/frontend/src/data/explorerOperations.ts +++ b/frontend/src/data/explorerOperations.ts @@ -21,7 +21,7 @@ const sharedOperationsNote = export const explorerFeaturePages = { bridge: { - eyebrow: 'Bridge Monitoring', + eyebrow: 'Bridge', title: 'Bridge & Relay Monitoring', description: 'Inspect the CCIP relay status, follow the live mission-control stream, trace bridge transactions, and review the managed Mainnet, BSC, Avalanche, Avalanche cW, and Avalanche to Chain 138 lanes.', @@ -81,7 +81,7 @@ export const explorerFeaturePages = { title: 'Liquidity access', description: 'Review the public Chain 138 PMM access points, route helpers, and fallback execution endpoints.', href: '/liquidity', - label: 'Open liquidity access', + label: 'Open liquidity', }, { title: 'Pools inventory', @@ -93,7 +93,7 @@ export const explorerFeaturePages = { title: 'Bridge monitoring', description: 'Cross-check route availability with live relay and bridge health before operator actions.', href: '/bridge', - label: 'Open bridge monitoring', + label: 'Open bridge', }, { title: 'Operations hub', @@ -114,7 +114,7 @@ export const explorerFeaturePages = { title: 'Bridge monitoring', description: 'Start with relay and bridge health before reviewing WETH-specific flows.', href: '/bridge', - label: 'Open bridge monitoring', + label: 'Open bridge', }, { title: 'Visual command center', @@ -175,8 +175,8 @@ export const explorerFeaturePages = { ], }, operator: { - eyebrow: 'Operator Surface', - title: 'Operator Surface', + eyebrow: 'Operator', + title: 'Operator', description: 'Expose the public operator surface for bridge checks, route validation, planner providers, liquidity entry points, and documentation.', note: sharedOperationsNote, @@ -188,7 +188,7 @@ export const explorerFeaturePages = { title: 'Bridge monitoring', description: 'Open relay status, queue posture, and bridge trace tools.', href: '/bridge', - label: 'Open bridge monitoring', + label: 'Open bridge', }, { title: 'Routes', @@ -200,7 +200,7 @@ export const explorerFeaturePages = { title: 'Liquidity access', description: 'Open partner payload helpers, route APIs, and execution-plan endpoints.', href: '/liquidity', - label: 'Open liquidity access', + label: 'Open liquidity', }, { title: 'Explorer docs', @@ -235,7 +235,7 @@ export const explorerFeaturePages = { title: 'Bridge monitoring', description: 'Correlate topology context with the live bridge and relay status surface.', href: '/bridge', - label: 'Open bridge monitoring', + label: 'Open bridge', }, { title: 'Explorer docs', @@ -252,8 +252,8 @@ export const explorerFeaturePages = { ], }, operations: { - eyebrow: 'Operations Hub', - title: 'Operations Hub', + eyebrow: 'Operations hub', + title: 'Operations hub', description: 'This hub exposes the public operational surfaces for bridge monitoring, routes, wrapped-asset references, analytics shortcuts, operator links, and topology views.', note: sharedOperationsNote, @@ -262,7 +262,7 @@ export const explorerFeaturePages = { title: 'Bridge & relay monitoring', description: 'Open mission-control status, SSE monitoring, and bridge trace helpers.', href: '/bridge', - label: 'Open bridge monitoring', + label: 'Open bridge', }, { title: 'Routes & liquidity', diff --git a/scripts/e2e-sprint-smoke.spec.ts b/scripts/e2e-sprint-smoke.spec.ts index 7167cf8..9bcd551 100644 --- a/scripts/e2e-sprint-smoke.spec.ts +++ b/scripts/e2e-sprint-smoke.spec.ts @@ -15,6 +15,11 @@ test.describe('Explorer sprint smoke', () => { await expect(page.getByRole('heading', { name: /Wallet Tools/i })).toBeVisible({ timeout: 10000 }) }) + test('wallet page shows WalletConnect posture note', async ({ page }) => { + await page.goto(`${EXPLORER_URL}/wallet`, { waitUntil: 'domcontentloaded', timeout: 20000 }) + await expect(page.getByText(/WalletConnect v2 posture/i)).toBeVisible({ timeout: 10000 }) + }) + test('tokens page loads', async ({ page }) => { await page.goto(`${EXPLORER_URL}/tokens`, { waitUntil: 'domcontentloaded', timeout: 20000 }) await expect(page.getByRole('heading', { name: /^Tokens$/i })).toBeVisible({ timeout: 10000 }) @@ -28,7 +33,7 @@ test.describe('Explorer sprint smoke', () => { test('operations hub loads extended token list note', async ({ page }) => { await page.goto(`${EXPLORER_URL}/operations`, { waitUntil: 'networkidle', timeout: 30000 }) - await expect(page.getByRole('heading', { name: /Operations Hub/i })).toBeVisible({ timeout: 10000 }) + await expect(page.getByRole('heading', { name: /Operations hub/i })).toBeVisible({ timeout: 10000 }) await expect(page.getByText(/Extended Metamask dual-chain catalog/i).first()).toBeVisible({ timeout: 10000 }) }) @@ -49,9 +54,15 @@ test.describe('Explorer sprint smoke', () => { await expect(page.getByRole('contentinfo').getByText(/Public APIs/i)).toBeVisible({ timeout: 10000 }) await expect(page.getByRole('contentinfo').getByRole('link', { name: /Blockscout stats/i })).toBeVisible({ timeout: 10000 }) await expect(page.getByRole('contentinfo').getByRole('link', { name: /Wallet tools/i })).toBeVisible({ timeout: 10000 }) + await expect(page.getByRole('contentinfo').getByRole('button', { name: /Copy URL for Blockscout stats/i })).toBeVisible({ timeout: 10000 }) await expect(page.getByRole('contentinfo').getByRole('link', { name: /Account access/i })).toBeVisible({ timeout: 10000 }) }) + test('homepage quick links include account access', async ({ page }) => { + await page.goto(`${EXPLORER_URL}/`, { waitUntil: 'domcontentloaded', timeout: 20000 }) + await expect(page.getByRole('link', { name: /Account access/i }).first()).toBeVisible({ timeout: 10000 }) + }) + test('tablet viewport exposes mobile navigation menu', async ({ page }) => { await page.setViewportSize({ width: 1100, height: 800 }) await page.goto(`${EXPLORER_URL}/`, { waitUntil: 'domcontentloaded', timeout: 20000 }) @@ -68,7 +79,7 @@ test.describe('Explorer sprint smoke', () => { test('operator page shows track 4 surface note', async ({ page }) => { await page.goto(`${EXPLORER_URL}/operator`, { waitUntil: 'domcontentloaded', timeout: 30000 }) - await expect(page.getByRole('heading', { name: /^Operator Surface$/i })).toBeVisible({ timeout: 15000 }) + await expect(page.getByRole('heading', { name: /^Operator$/i })).toBeVisible({ timeout: 15000 }) await expect(page.getByText(/Track 4 public surface/i).first()).toBeVisible({ timeout: 10000 }) })