first day of vibe coding

This commit is contained in:
k
2026-04-20 22:11:18 +08:00
commit 1747fc489f
38 changed files with 15267 additions and 0 deletions
+65
View File
@@ -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>
)
}
+33
View File
@@ -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>
)
}
+14
View File
@@ -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>
)
}
+22
View File
@@ -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;
}
+42
View File
@@ -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>
)
}
+20
View File
@@ -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 : []}
/>
)
}
+15
View File
@@ -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>
)
}
+31
View File
@@ -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>
)
}
+13
View File
@@ -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>
)
}
+17
View File
@@ -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>
)
}