feat: revamp dashboard, trades, and add landing/legal pages

- Major UI updates across dashboard, analytics, posts, trades, settings
- New landing page, robots/sitemap, contact/privacy/terms pages
- Updated globals.css with extensive styling and new landing.css
- Refactor signedRequest, realtime data hook, and dashboard store

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
k
2026-04-25 16:04:57 +08:00
parent 83e5892ddf
commit 040e1df685
28 changed files with 4133 additions and 690 deletions
+45 -11
View File
@@ -1,10 +1,10 @@
'use client'
import { useState } from 'react'
import { useAccount, useSignMessage } from 'wagmi'
import { useEffect, useState } from 'react'
import { useAccount, useConnect, useSignMessage } from 'wagmi'
import type { BotPerformance } from '@/types'
import { useDashboardStore } from '@/store/dashboard'
import { setHlApiKey, subscribe } from '@/lib/api'
import { getUserPublic, setHlApiKey, subscribe } from '@/lib/api'
import { signRequest } from '@/lib/signedRequest'
// Action names must match backend/app/api/{user,subscribe}.py
@@ -25,8 +25,9 @@ function fmtHold(s: number) {
}
export default function BotPanel({ performance }: Props) {
const { isSubscribed, hlApiKeySet, hlApiKeyMasked, setHlApiKeySet, setSubscribed } = useDashboardStore()
const { isSubscribed, hlApiKeySet, hlApiKeyMasked, botReadiness, setBotReadiness, setHlApiKeySet, setSubscribed } = useDashboardStore()
const { address, isConnected } = useAccount()
const { connect, connectors } = useConnect()
const { signMessageAsync } = useSignMessage()
const [apiKey, setApiKey] = useState('')
@@ -35,6 +36,33 @@ export default function BotPanel({ performance }: Props) {
const [subState, setSubState] = useState<'idle' | 'signing' | 'saving' | 'error'>('idle')
const [subError, setSubError] = useState('')
useEffect(() => {
if (!isConnected || !address) {
setSubscribed(false)
setHlApiKeySet(false)
setBotReadiness('unknown')
return
}
setSubscribed(false)
setHlApiKeySet(false)
setBotReadiness('unknown')
getUserPublic(address.toLowerCase())
.then((user) => {
setSubscribed(user.active)
setHlApiKeySet(user.hl_api_key_set)
setBotReadiness(user.hl_api_key_set ? 'saved' : 'unknown')
})
.catch(() => {})
}, [address, isConnected, setHlApiKeySet, setSubscribed, setBotReadiness])
async function refreshUserState(wallet: string) {
const pub = await getUserPublic(wallet.toLowerCase())
setSubscribed(pub.active)
setHlApiKeySet(pub.hl_api_key_set)
setBotReadiness(pub.hl_api_key_set ? 'saved' : 'unknown')
return pub
}
async function handleSubscribe() {
if (!address) return
setSubError('')
@@ -48,7 +76,7 @@ export default function BotPanel({ performance }: Props) {
})
setSubState('saving')
await subscribe(env)
setSubscribed(true)
await refreshUserState(address)
setSubState('idle')
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Unknown error'
@@ -76,7 +104,8 @@ export default function BotPanel({ performance }: Props) {
})
setSaveState('saving')
const res = await setHlApiKey(env, trimmed)
setHlApiKeySet(true, res.masked_key)
const pub = await refreshUserState(address)
setHlApiKeySet(pub.hl_api_key_set, res.masked_key)
setApiKey('')
setSaveState('success')
} catch (err: unknown) {
@@ -96,13 +125,18 @@ export default function BotPanel({ performance }: Props) {
: saveState === 'success' ? '✓ Saved'
: 'Save key'
function handleConnectWallet() {
const connector = connectors[0]
if (connector) connect({ connector })
}
return (
<>
{/* Bot status card — dark design */}
<div className="bot-status">
<div className="bot-head">
<h3>
<span style={{ width: 8, height: 8, borderRadius: 999, background: isSubscribed ? 'var(--amber)' : 'oklch(70% 0.01 85)', display: 'inline-block' }} />
<span style={{ width: 8, height: 8, borderRadius: 999, background: botReadiness === 'ready' ? 'var(--amber)' : hlApiKeySet ? 'var(--up)' : isSubscribed ? 'var(--up)' : 'oklch(70% 0.01 85)', display: 'inline-block' }} />
Auto-trader
</h3>
<span style={{ fontSize: 11, opacity: 0.7, textTransform: 'uppercase', letterSpacing: '0.08em' }}>30 days</span>
@@ -112,7 +146,7 @@ export default function BotPanel({ performance }: Props) {
<div className="bot-stat">
<div className="k">Net P&amp;L</div>
<div className="v amber">
{performance ? (performance.net_pnl_usd >= 0 ? '+$' : '-$') + Math.abs(performance.net_pnl_usd).toLocaleString(undefined, { maximumFractionDigits: 0 }) : '—'}
{performance ? (performance.net_pnl_usd >= 0 ? '+$' : '-$') + Math.abs(performance.net_pnl_usd).toLocaleString('en-US', { maximumFractionDigits: 0 }) : '—'}
</div>
</div>
<div className="bot-stat">
@@ -134,7 +168,7 @@ export default function BotPanel({ performance }: Props) {
<div className="bot-cta">
{!isConnected && (
<button className="btn amber" style={{ width: '100%' }}
onClick={() => document.querySelector<HTMLButtonElement>('.connect-btn')?.click()}>
onClick={handleConnectWallet}>
Connect wallet
</button>
)}
@@ -155,12 +189,12 @@ export default function BotPanel({ performance }: Props) {
)}
{isConnected && isSubscribed && !hlApiKeySet && (
<div style={{ width: '100%', textAlign: 'center', fontSize: 12, color: 'oklch(75% 0.01 85)', padding: '6px 0' }}>
Paste your Hyperliquid API key below to activate
Paste your Hyperliquid API key below to finish setup
</div>
)}
{isConnected && isSubscribed && hlApiKeySet && (
<div style={{ width: '100%', textAlign: 'center', fontSize: 12, color: 'var(--amber)', padding: '6px 0', fontWeight: 500 }}>
Bot active · waiting for next signal
Setup saved · verification still depends on backend
</div>
)}
</div>