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 <cursoragent@cursor.com>
This commit is contained in:
@@ -15,7 +15,7 @@ export default function BrandLockup({ compact = false }: { compact?: boolean })
|
||||
</span>
|
||||
<span
|
||||
className={[
|
||||
'block truncate font-medium uppercase text-gray-500 dark:text-gray-400',
|
||||
'block truncate font-medium uppercase text-gray-500 dark:text-gray-400 max-sm:hidden',
|
||||
compact ? 'text-[0.64rem] tracking-[0.13em]' : 'text-[0.68rem] tracking-[0.12em]',
|
||||
].join(' ')}
|
||||
>
|
||||
|
||||
34
frontend/src/components/common/ExplorerRetryAlert.tsx
Normal file
34
frontend/src/components/common/ExplorerRetryAlert.tsx
Normal file
@@ -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 (
|
||||
<div
|
||||
role="alert"
|
||||
className={[
|
||||
'flex flex-col gap-3 rounded-xl border border-red-200 bg-red-50/70 px-4 py-3 dark:border-red-900/50 dark:bg-red-950/20 sm:flex-row sm:items-center sm:justify-between',
|
||||
className,
|
||||
].join(' ')}
|
||||
>
|
||||
<p className="text-sm leading-6 text-red-900 dark:text-red-100">{message}</p>
|
||||
{onRetry ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRetry}
|
||||
className="shrink-0 rounded-lg border border-red-300 bg-white px-3 py-1.5 text-sm font-semibold text-red-800 transition-colors hover:bg-red-50 dark:border-red-800 dark:bg-red-950 dark:text-red-100 dark:hover:bg-red-900/40"
|
||||
>
|
||||
{retryLabel}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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() {
|
||||
<div className="mb-3 text-sm font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
Public APIs
|
||||
</div>
|
||||
<ul className="space-y-3 text-sm">
|
||||
{explorerPublicApiLinks.map((link) => (
|
||||
<li key={link.href}>
|
||||
<a className={footerLinkClass} href={link.href} target="_blank" rel="noopener noreferrer">
|
||||
{link.label}
|
||||
</a>
|
||||
<p className="mt-0.5 text-xs leading-5 text-gray-500 dark:text-gray-500">{link.description}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<FooterPublicApiLinks />
|
||||
<p className="mt-3 text-xs leading-5 text-gray-500 dark:text-gray-500">
|
||||
Read-only JSON endpoints on the public explorer domain. No API key required.
|
||||
</p>
|
||||
|
||||
53
frontend/src/components/common/FooterPublicApiLinks.tsx
Normal file
53
frontend/src/components/common/FooterPublicApiLinks.tsx
Normal file
@@ -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<string | null>(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 (
|
||||
<ul className="space-y-3 text-sm">
|
||||
{explorerPublicApiLinks.map((link) => (
|
||||
<li key={link.href}>
|
||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<a className={footerLinkClass} href={link.href} target="_blank" rel="noopener noreferrer">
|
||||
{link.label}
|
||||
</a>
|
||||
<p className="mt-0.5 text-xs leading-5 text-gray-500 dark:text-gray-500">{link.description}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void copyUrl(link.href)}
|
||||
className="shrink-0 rounded-lg border border-gray-200 px-2.5 py-1 text-xs font-medium text-gray-700 transition-colors hover:border-primary-300 hover:text-primary-700 dark:border-gray-700 dark:text-gray-200 dark:hover:border-primary-500 dark:hover:text-primary-300"
|
||||
aria-label={`Copy URL for ${link.label}`}
|
||||
>
|
||||
{copiedHref === link.href ? 'Copied' : 'Copy URL'}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@@ -717,7 +717,7 @@ export default function Navbar() {
|
||||
<div className="flex min-h-[60px] items-center gap-3 lg:min-h-[64px]">
|
||||
<Link
|
||||
href="/"
|
||||
className="group inline-flex min-w-0 items-center gap-2 rounded-lg py-1.5 pr-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-950"
|
||||
className="group inline-flex shrink-0 items-center gap-2 rounded-lg py-1.5 pr-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-950 max-sm:max-w-[9.5rem] sm:max-w-none"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
aria-label="Go to DBIS Explorer home"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user