Defer heavy getServerSideProps on home, operator, addresses, and wallet to cut TTFB; centralize locale-safe formatters with client-only relative times; add compact ops UX, bridge/operator relay pagination, and Playwright route/scroll smoke in deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
58 lines
1.1 KiB
TypeScript
58 lines
1.1 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { formatRelativeAgeAt } from '@/utils/format'
|
|
|
|
const PLACEHOLDER = '…'
|
|
|
|
export default function ClientRelativeTime({
|
|
value,
|
|
prefix = '',
|
|
suffix = '',
|
|
fallback = 'Unknown',
|
|
className,
|
|
}: {
|
|
value?: string | null
|
|
prefix?: string
|
|
suffix?: string
|
|
fallback?: string
|
|
className?: string
|
|
}) {
|
|
const [label, setLabel] = useState(PLACEHOLDER)
|
|
|
|
useEffect(() => {
|
|
if (!value) {
|
|
setLabel(fallback)
|
|
return
|
|
}
|
|
const parsed = Date.parse(value)
|
|
if (!Number.isFinite(parsed)) {
|
|
setLabel(fallback)
|
|
return
|
|
}
|
|
const update = () => setLabel(formatRelativeAgeAt(Date.now(), value))
|
|
update()
|
|
const id = window.setInterval(update, 30_000)
|
|
return () => window.clearInterval(id)
|
|
}, [value, fallback])
|
|
|
|
const content = label === PLACEHOLDER ? PLACEHOLDER : label
|
|
if (className) {
|
|
return (
|
|
<span className={className} suppressHydrationWarning>
|
|
{prefix}
|
|
{content}
|
|
{suffix}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<span suppressHydrationWarning>
|
|
{prefix}
|
|
{content}
|
|
{suffix}
|
|
</span>
|
|
)
|
|
}
|