first day of vibe coding
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import type { TrumpPost, BotTrade, BotPerformance, Candle } from '@/types'
|
||||
import { useDashboardStore } from '@/store/dashboard'
|
||||
import { usePriceSocket } from '@/lib/useRealtimeData'
|
||||
import { getPrices } from '@/lib/api'
|
||||
import KpiRow from '@/components/dashboard/KpiRow'
|
||||
import ChartPanel from '@/components/dashboard/ChartPanel'
|
||||
import BotPanel from '@/components/dashboard/BotPanel'
|
||||
import TradesTable from '@/components/trades/TradesTable'
|
||||
|
||||
interface Props {
|
||||
initialPosts: TrumpPost[]
|
||||
initialPerformance?: BotPerformance
|
||||
initialTrades: BotTrade[]
|
||||
}
|
||||
|
||||
export default function DashboardClient({ initialPosts, initialPerformance, initialTrades }: Props) {
|
||||
const { asset, timeframe, setLivePrice } = useDashboardStore()
|
||||
const [posts, setPosts] = useState<TrumpPost[]>(initialPosts)
|
||||
const [candles, setCandles] = useState<Candle[]>([])
|
||||
const [candlesLoading, setCandlesLoading] = useState(false)
|
||||
|
||||
usePriceSocket({
|
||||
onPrice: (a, price) => setLivePrice(a, price),
|
||||
onNewPost: (post) => setPosts((prev) => [post as TrumpPost, ...prev].slice(0, 50)),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setCandles([])
|
||||
setCandlesLoading(true)
|
||||
getPrices(asset, timeframe)
|
||||
.then(setCandles)
|
||||
.catch(() => {})
|
||||
.finally(() => setCandlesLoading(false))
|
||||
}, [asset, timeframe])
|
||||
|
||||
return (
|
||||
<div className="pt-14">
|
||||
<div className="max-w-[1400px] mx-auto px-6 py-6 flex flex-col gap-4">
|
||||
{/* KPI 行 */}
|
||||
<KpiRow performance={initialPerformance} postsCount={posts.length} />
|
||||
|
||||
{/* 主内容区:图表 + Bot 面板 */}
|
||||
<div className="flex gap-4 items-start w-full">
|
||||
<div className="flex-1 min-w-0 relative">
|
||||
<ChartPanel posts={posts} candles={candles} />
|
||||
{candlesLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/40 rounded-[10px] pointer-events-none">
|
||||
<span className="text-[#555555] text-sm">Loading...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-[280px] shrink-0">
|
||||
<BotPanel performance={initialPerformance} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 交易记录 */}
|
||||
<TradesTable trades={initialTrades} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
|
||||
import { WagmiProvider } from 'wagmi'
|
||||
import { RainbowKitProvider, darkTheme } from '@rainbow-me/rainbowkit'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { config } from '@/lib/wagmi'
|
||||
import '@rainbow-me/rainbowkit/styles.css'
|
||||
|
||||
interface ProvidersProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function Providers({ children }: ProvidersProps) {
|
||||
const [queryClient] = useState(() => new QueryClient())
|
||||
|
||||
return (
|
||||
<WagmiProvider config={config}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RainbowKitProvider
|
||||
theme={darkTheme({
|
||||
accentColor: '#f97316',
|
||||
accentColorForeground: '#000000',
|
||||
borderRadius: 'medium',
|
||||
overlayBlur: 'small',
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</RainbowKitProvider>
|
||||
</QueryClientProvider>
|
||||
</WagmiProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getTranslations } from 'next-intl/server'
|
||||
|
||||
export default async function AnalyticsPage() {
|
||||
const t = await getTranslations('analytics')
|
||||
|
||||
return (
|
||||
<div className="max-w-[1400px] mx-auto px-6 py-6">
|
||||
<div className="bg-[#0a0a0a] border border-[#141414] rounded-[10px] p-10 text-center">
|
||||
<h1 className="text-[22px] font-medium text-white mb-3">{t('title')}</h1>
|
||||
<p className="text-[14px] text-[#555555]">{t('comingSoon')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
/* Remove default focus ring and apply custom */
|
||||
button:focus-visible {
|
||||
outline: 1px solid #f97316;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
outline: 1px solid #222222;
|
||||
outline-offset: 0;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import { getMessages } from 'next-intl/server'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { locales } from '@/i18n'
|
||||
import Navbar from '@/components/nav/Navbar'
|
||||
import Providers from './Providers'
|
||||
import './globals.css'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'TrumpSignal — AI-powered crypto trading signals',
|
||||
description: 'Trade crypto based on Trump social media signals with AI confidence scoring.',
|
||||
}
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode
|
||||
params: { locale: string }
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({ children, params: { locale } }: LayoutProps) {
|
||||
if (!locales.includes(locale as (typeof locales)[number])) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const messages = await getMessages()
|
||||
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<body className={`${inter.className} bg-[#000000] min-h-screen text-white`}>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<Providers>
|
||||
<Navbar locale={locale} />
|
||||
<main className="pt-14">{children}</main>
|
||||
</Providers>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getPosts, getPerformance, getTrades } from '@/lib/api'
|
||||
import DashboardClient from './DashboardClient'
|
||||
|
||||
export const revalidate = 30
|
||||
|
||||
export default async function OverviewPage() {
|
||||
const [posts, performance, trades] = await Promise.allSettled([
|
||||
getPosts(500, 1),
|
||||
getPerformance(),
|
||||
getTrades(20, 1),
|
||||
])
|
||||
|
||||
return (
|
||||
<DashboardClient
|
||||
initialPosts={posts.status === 'fulfilled' ? posts.value : []}
|
||||
initialPerformance={performance.status === 'fulfilled' ? performance.value : undefined}
|
||||
initialTrades={trades.status === 'fulfilled' ? trades.value : []}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { getTranslations } from 'next-intl/server'
|
||||
|
||||
export default async function PostsPage() {
|
||||
const t = await getTranslations('posts')
|
||||
|
||||
return (
|
||||
<div className="max-w-[1400px] mx-auto px-6 py-6">
|
||||
<div className="bg-[#0a0a0a] border border-[#141414] rounded-[10px] p-10 text-center">
|
||||
<h1 className="text-[22px] font-medium text-white mb-3">{t('title')}</h1>
|
||||
<p className="text-[14px] text-[#555555]">{t('comingSoon')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useAccount } from 'wagmi'
|
||||
import { useConnectModal } from '@rainbow-me/rainbowkit'
|
||||
|
||||
export default function SettingsClient() {
|
||||
const t = useTranslations('settings')
|
||||
const { isConnected } = useAccount()
|
||||
const { openConnectModal } = useConnectModal()
|
||||
|
||||
if (!isConnected) {
|
||||
return (
|
||||
<div className="bg-[#0a0a0a] border border-[#141414] rounded-[10px] p-10 text-center">
|
||||
<p className="text-[14px] text-[#555555] mb-4">{t('connectRequired')}</p>
|
||||
<button
|
||||
onClick={openConnectModal}
|
||||
className="bg-[#f97316] text-black font-medium text-[13px] rounded-lg px-6 py-2.5 hover:bg-[#fb923c] transition-colors"
|
||||
>
|
||||
Connect wallet
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-[#0a0a0a] border border-[#141414] rounded-[10px] p-10 text-center">
|
||||
<p className="text-[14px] text-[#555555]">{t('comingSoon')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getTranslations } from 'next-intl/server'
|
||||
import SettingsClient from './SettingsClient'
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const t = await getTranslations('settings')
|
||||
|
||||
return (
|
||||
<div className="max-w-[1400px] mx-auto px-6 py-6">
|
||||
<h1 className="text-[22px] font-medium text-white mb-5">{t('title')}</h1>
|
||||
<SettingsClient />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { getTranslations } from 'next-intl/server'
|
||||
import TradesTable from '@/components/trades/TradesTable'
|
||||
import { getTrades } from '@/lib/api'
|
||||
|
||||
export const revalidate = 10
|
||||
|
||||
export default async function TradesPage() {
|
||||
const t = await getTranslations('trades')
|
||||
const trades = await getTrades(100, 1).catch(() => [])
|
||||
|
||||
return (
|
||||
<div className="max-w-[1400px] mx-auto px-6 py-6 pt-20">
|
||||
<h1 className="text-[22px] font-medium text-white mb-5">{t('title')}</h1>
|
||||
<TradesTable trades={trades} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'
|
||||
|
||||
async function handler(request: NextRequest): Promise<NextResponse> {
|
||||
const url = new URL(request.url)
|
||||
const targetPath = url.pathname.replace('/api/proxy', '')
|
||||
const targetUrl = `${API_BASE}${targetPath}${url.search}`
|
||||
|
||||
const headers = new Headers(request.headers)
|
||||
// Remove Next.js internal headers
|
||||
headers.delete('host')
|
||||
headers.delete('x-forwarded-for')
|
||||
headers.delete('x-forwarded-host')
|
||||
headers.delete('x-forwarded-proto')
|
||||
|
||||
const body =
|
||||
request.method !== 'GET' && request.method !== 'HEAD'
|
||||
? await request.arrayBuffer()
|
||||
: undefined
|
||||
|
||||
try {
|
||||
const upstream = await fetch(targetUrl, {
|
||||
method: request.method,
|
||||
headers,
|
||||
body,
|
||||
})
|
||||
|
||||
const responseHeaders = new Headers(upstream.headers)
|
||||
responseHeaders.delete('transfer-encoding')
|
||||
|
||||
const responseBody = await upstream.arrayBuffer()
|
||||
|
||||
return new NextResponse(responseBody, {
|
||||
status: upstream.status,
|
||||
statusText: upstream.statusText,
|
||||
headers: responseHeaders,
|
||||
})
|
||||
} catch (err) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Upstream request failed', detail: String(err) },
|
||||
{ status: 502 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const GET = handler
|
||||
export const POST = handler
|
||||
export const PUT = handler
|
||||
export const PATCH = handler
|
||||
export const DELETE = handler
|
||||
export const HEAD = handler
|
||||
export const OPTIONS = handler
|
||||
Reference in New Issue
Block a user