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:
@@ -52,6 +52,9 @@ export interface UserSettings {
|
||||
take_profit_pct: number | null
|
||||
stop_loss_pct: number | null
|
||||
min_confidence: number
|
||||
daily_budget_usd: number | null
|
||||
active_from: string | null // ISO UTC
|
||||
active_until: string | null // ISO UTC
|
||||
}
|
||||
|
||||
export interface UserData {
|
||||
|
||||
+23
-1
@@ -55,6 +55,25 @@ export interface SignedEnvelope {
|
||||
*/
|
||||
const VIEW_TTL_MS = 4 * 60 * 1000
|
||||
|
||||
/**
|
||||
* Read-only: returns a cached, not-yet-expired view envelope if one exists.
|
||||
* Never signs — never triggers a wallet popup. Returns null if no fresh cache.
|
||||
*/
|
||||
export function getCachedViewEnvelope(action: string, wallet: string): SignedEnvelope | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
const cacheKey = `ts:view:${action}:${wallet.toLowerCase()}`
|
||||
const raw = sessionStorage.getItem(cacheKey)
|
||||
if (!raw) return null
|
||||
try {
|
||||
const env = JSON.parse(raw) as SignedEnvelope
|
||||
if (Date.now() - env.timestamp < VIEW_TTL_MS) return env
|
||||
} catch (err) {
|
||||
console.warn('[signedRequest] corrupt cached envelope, ignoring', err)
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function getOrCreateViewEnvelope(params: {
|
||||
action: string
|
||||
wallet: string
|
||||
@@ -68,7 +87,10 @@ export async function getOrCreateViewEnvelope(params: {
|
||||
try {
|
||||
const env = JSON.parse(raw) as SignedEnvelope
|
||||
if (Date.now() - env.timestamp < VIEW_TTL_MS) return env
|
||||
} catch {}
|
||||
} catch (err) {
|
||||
console.warn('[signedRequest] corrupt cached envelope, ignoring', err)
|
||||
sessionStorage.removeItem(cacheKey)
|
||||
}
|
||||
}
|
||||
const env = await signRequest({
|
||||
action: params.action,
|
||||
|
||||
+10
-1
@@ -16,9 +16,12 @@ interface Handlers {
|
||||
export function usePriceSocket(handlers: Handlers) {
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
const handlersRef = useRef(handlers)
|
||||
const unmountedRef = useRef(false)
|
||||
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
handlersRef.current = handlers
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (unmountedRef.current) return
|
||||
const ws = new WebSocket(`${WS_URL}/ws/prices`)
|
||||
wsRef.current = ws
|
||||
|
||||
@@ -36,7 +39,10 @@ export function usePriceSocket(handlers: Handlers) {
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
setTimeout(connect, 3000)
|
||||
// Don't schedule a reconnect if the component already unmounted —
|
||||
// otherwise we leak a socket per navigation forever.
|
||||
if (unmountedRef.current) return
|
||||
reconnectTimerRef.current = setTimeout(connect, 3000)
|
||||
}
|
||||
|
||||
ws.onerror = () => {
|
||||
@@ -45,8 +51,11 @@ export function usePriceSocket(handlers: Handlers) {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
unmountedRef.current = false
|
||||
connect()
|
||||
return () => {
|
||||
unmountedRef.current = true
|
||||
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current)
|
||||
wsRef.current?.close()
|
||||
}
|
||||
}, [connect])
|
||||
|
||||
Reference in New Issue
Block a user