diff --git a/main.go b/main.go index 7fa7295c..a2c4f441 100644 --- a/main.go +++ b/main.go @@ -118,8 +118,12 @@ func main() { if t.IsRunning { status = "✅ Running" } - logger.Infof(" • %s [%s] %s - AI Model: %s, Exchange: %s", - t.Name, t.ID[:8], status, t.AIModelID, t.ExchangeID) + idShort := t.ID + if len(idShort) > 8 { + idShort = idShort[:8] + } + logger.Infof(" • %s [%s] %s - AI Model: %s, Exchange: %s", + t.Name, idShort, status, t.AIModelID, t.ExchangeID) } } diff --git a/web/src/components/modals/SetupPage.tsx b/web/src/components/modals/SetupPage.tsx index f43b848f..19d9635f 100644 --- a/web/src/components/modals/SetupPage.tsx +++ b/web/src/components/modals/SetupPage.tsx @@ -1,14 +1,62 @@ import React, { useState } from 'react' -import { Eye, EyeOff } from 'lucide-react' +import { Eye, EyeOff, Globe } from 'lucide-react' import { useAuth } from '../../contexts/AuthContext' -import { DeepVoidBackground } from '../common/DeepVoidBackground' import { invalidateSystemConfig } from '../../lib/config' import { OnboardingModeSelector } from '../auth/OnboardingModeSelector' import type { UserMode } from '../../lib/onboarding' import { useLanguage } from '../../contexts/LanguageContext' +import type { Language } from '../../i18n/translations' + +const labels = { + zh: { + welcome: '欢迎使用 NOFX', + subtitle: '创建账号开始使用', + email: '邮箱', + emailPlaceholder: 'you@example.com', + password: '密码', + passwordPlaceholder: '至少 8 个字符', + passwordError: '密码至少需要 8 个字符', + submit: '开始使用', + submitting: '创建中...', + setupFailed: '创建失败,请重试', + singleUser: '单用户系统 — 这是唯一的账号', + }, + en: { + welcome: 'Welcome to NOFX', + subtitle: 'Create your account to get started', + email: 'Email', + emailPlaceholder: 'you@example.com', + password: 'Password', + passwordPlaceholder: 'At least 8 characters', + passwordError: 'Password must be at least 8 characters', + submit: 'Get Started', + submitting: 'Creating account...', + setupFailed: 'Setup failed, please try again', + singleUser: 'Single-user system — this is the only account', + }, + id: { + welcome: 'Selamat Datang di NOFX', + subtitle: 'Buat akun untuk memulai', + email: 'Email', + emailPlaceholder: 'you@example.com', + password: 'Kata Sandi', + passwordPlaceholder: 'Minimal 8 karakter', + passwordError: 'Kata sandi minimal 8 karakter', + submit: 'Mulai', + submitting: 'Membuat akun...', + setupFailed: 'Gagal membuat akun, coba lagi', + singleUser: 'Sistem pengguna tunggal — ini satu-satunya akun', + }, +} as const + +const langOptions: { value: Language; label: string }[] = [ + { value: 'en', label: 'English' }, + { value: 'zh', label: '中文' }, + { value: 'id', label: 'Bahasa' }, +] export function SetupPage() { - const { language } = useLanguage() + const { language, setLanguage } = useLanguage() const { register } = useAuth() const [email, setEmail] = useState('') const [password, setPassword] = useState('') @@ -17,11 +65,13 @@ export function SetupPage() { const [loading, setLoading] = useState(false) const [mode, setMode] = useState('beginner') + const l = labels[language as keyof typeof labels] || labels.en + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError('') if (password.length < 8) { - setError('Password must be at least 8 characters') + setError(l.passwordError) return } setLoading(true) @@ -30,40 +80,100 @@ export function SetupPage() { if (result.success) { invalidateSystemConfig() } else { - setError(result.message || 'Setup failed, please try again') + setError(result.message || l.setupFailed) } } return ( - -
-
+
+ {/* Decorative background - simulates the main app behind a modal */} + + {/* Grid */} +
+
+
+ + {/* Glow spots */} +
+
+
+
+
+ + {/* Faux UI elements in background to simulate the app */} +
+ {/* Fake header bar */} +
+
+
+
+
+
+
+ {/* Fake content cards */} +
+ {[...Array(4)].map((_, i) => ( +
+ ))} +
+
+
+
+
+ + {/* Blur overlay */} +
+ + {/* Language switcher */} +
+
+ + {langOptions.map((opt) => ( + + ))} +
+
+ + {/* Modal card */} +
+
{/* Logo + Title */} -
-
+
+
-
- NOFX +
+ NOFX
-

Welcome to NOFX

-

Create your account to get started

+

{l.welcome}

+

{l.subtitle}

{/* Card */} -
+
{/* Email */}
- + setEmail(e.target.value)} - className="w-full bg-zinc-950/80 border border-zinc-700/80 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-nofx-gold/60 focus:ring-1 focus:ring-nofx-gold/30 transition-all" - placeholder="you@example.com" + className="w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-nofx-gold/60 focus:ring-1 focus:ring-nofx-gold/30 transition-all" + placeholder={l.emailPlaceholder} required autoFocus /> @@ -71,14 +181,14 @@ export function SetupPage() { {/* Password */}
- +
setPassword(e.target.value)} - className="w-full bg-zinc-950/80 border border-zinc-700/80 rounded-xl px-4 py-3 pr-11 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-nofx-gold/60 focus:ring-1 focus:ring-nofx-gold/30 transition-all" - placeholder="At least 8 characters" + className="w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3 pr-11 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-nofx-gold/60 focus:ring-1 focus:ring-nofx-gold/30 transition-all" + placeholder={l.passwordPlaceholder} required />

- Single-user system — this is the only account + {l.singleUser}

- + + +
) } diff --git a/web/src/pages/BeginnerOnboardingPage.tsx b/web/src/pages/BeginnerOnboardingPage.tsx index a133ced2..17672eb0 100644 --- a/web/src/pages/BeginnerOnboardingPage.tsx +++ b/web/src/pages/BeginnerOnboardingPage.tsx @@ -1,5 +1,5 @@ -import { useEffect, useMemo, useRef, useState } from 'react' -import { Copy, Eye, EyeOff, RefreshCw, Shield, Wallet, Sparkles } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' +import { Copy, Eye, EyeOff, RefreshCw, Shield, Wallet } from 'lucide-react' import { QRCodeSVG } from 'qrcode.react' import { toast } from 'sonner' import { DeepVoidBackground } from '../components/common/DeepVoidBackground' @@ -13,64 +13,33 @@ export function BeginnerOnboardingPage() { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState('') - const [showPrivateKey, setShowPrivateKey] = useState(false) + const [showPrivateKey, setShowPrivateKey] = useState(true) const [refreshingBalance, setRefreshingBalance] = useState(false) const hasRequestedRef = useRef(false) const isZh = language === 'zh' const loadOnboarding = async (showLoading: boolean) => { - if (showLoading) { - setLoading(true) - } else { - setRefreshingBalance(true) - } - + if (showLoading) setLoading(true) + else setRefreshingBalance(true) setError('') try { const result = await api.prepareBeginnerOnboarding() setData(result) setBeginnerWalletAddress(result.address) } catch (err) { - setError( - err instanceof Error - ? err.message - : isZh - ? '新手钱包准备失败' - : 'Failed to prepare beginner wallet' - ) + setError(err instanceof Error ? err.message : isZh ? '新手钱包准备失败' : 'Failed to prepare beginner wallet') } finally { - if (showLoading) { - setLoading(false) - } else { - setRefreshingBalance(false) - } + if (showLoading) setLoading(false) + else setRefreshingBalance(false) } } useEffect(() => { - if (hasRequestedRef.current) { - return - } + if (hasRequestedRef.current) return hasRequestedRef.current = true void loadOnboarding(true) }, []) - const hints = useMemo( - () => - isZh - ? [ - '这是你的专属 Base 钱包,只用于后续调用大模型。', - '请保存私钥。丢失后无法恢复。', - '只往这个地址充值 Base 链 USDC,不要充到别的链。', - ] - : [ - 'This dedicated Base wallet is only used to pay for model calls.', - 'Save the private key now. It cannot be recovered later.', - 'Deposit USDC on Base only. Do not send funds from another chain.', - ], - [isZh] - ) - const copyText = async (value: string, label: string) => { try { await navigator.clipboard.writeText(value) @@ -87,177 +56,131 @@ export function BeginnerOnboardingPage() { return ( -
-
-
-
-
- -
-
-
- {isZh ? '新手保护' : 'Beginner Guard'} -
-

- {isZh ? '钱包已经帮你准备好了' : 'Your wallet is ready'} -

-
+
+ {/* Header - compact */} +
+
+ +
+
+
+ {isZh ? '新手保护' : 'Beginner Guard'}
+

+ {isZh ? '钱包已经帮你准备好了' : 'Your wallet is ready'} +

+
+
+ Claw402 + DeepSeek · {isZh ? '按次付费' : 'Pay per call'} +
+
-

- {isZh - ? '我们已经为你生成了一个专属钱包,并默认接入 Claw402 + DeepSeek。你现在只需要保存私钥,然后往这个地址充值 Base 链 USDC,后面调用大模型时会自动从这里扣费。' - : 'We generated a dedicated wallet for you and preconfigured Claw402 + DeepSeek. Save the private key, then deposit Base USDC to this address so future model calls can be paid automatically.'} -

+ {error ? ( +
{error}
+ ) : null} -
- {hints.map((hint) => ( -
- -
{hint}
-
- ))} + {/* Main card */} +
+ {loading ? ( +
+ {isZh ? '正在准备你的 Base 钱包...' : 'Preparing your Base wallet...'}
- -
-
- - {isZh ? '为什么要充值?' : 'Why fund this wallet?'} -
-

- {isZh - ? '这里只负责大模型调用费用,不会自动替你充值交易所。先充少量 USDC 就够了,通常 $5-$10 可以用很久。' - : 'This wallet only covers LLM usage costs. It does not fund your exchange automatically. A small amount of USDC is enough to get started, usually $5-$10.'} -

-
- - {error ? ( -
- {error} -
- ) : null} -
- -
- {loading ? ( -
- {isZh ? '正在准备你的 Base 钱包...' : 'Preparing your Base wallet...'} -
- ) : data ? ( -
-
-
- {isZh ? '默认模型' : 'Default Model'} -
-
Claw402 + DeepSeek
-
- {isZh ? '按次付费,无需 API Key' : 'Pay per call, no API key needed'} -
+ ) : data ? ( +
+ {/* Left: QR + Balance */} +
+
+
- -
-
- -
-
- {isZh ? '充值地址(Base 链 USDC)' : 'Deposit Address (Base USDC)'} -
-
- {data.address} +
+ {isZh ? '充值地址(Base USDC)' : 'Deposit (Base USDC)'} +
+
+
+
{data.balance_usdc} USDC
- -
-
-
-
- {isZh ? '当前余额' : 'Current Balance'} -
-
- {data.balance_usdc} USDC -
-
- {isZh ? 'Base 链钱包余额' : 'Base wallet balance'} -
-
- -
-
+
+ {isZh ? '$5-$10 可以用很久' : '$5-$10 lasts a long time'} +
+
-
-
-
-
- {isZh ? '钱包私钥' : 'Wallet Private Key'} -
-
- {isZh ? '请先备份,再进入下一步。' : 'Back this up before you continue.'} -
+ {/* Right: Address + Key + Action */} +
+ {/* Address */} +
+
+ + {isZh ? '钱包地址' : 'Wallet Address'} +
+
+
+ {data.address}
-
- {showPrivateKey ? data.private_key : '0x' + '•'.repeat(64)} -
-
-
-
- {data.env_saved - ? isZh - ? `已同步保存到环境文件:${data.env_path || '.env'}` - : `Also saved to env: ${data.env_path || '.env'}` - : isZh - ? '当前运行环境没有成功写回 .env,但产品已完成默认配置。' - : 'The app is configured, but this runtime could not write back to .env.'} + {/* Private Key */} +
+
+ + {isZh ? '私钥 — 请立即备份' : 'Private Key — back up now'} + +
+
+
+ {showPrivateKey ? data.private_key : '0x' + '•'.repeat(64)} +
+
- {data.env_warning ?
{data.env_warning}
: null}
+ {/* Tips */} +
+ {isZh + ? '• 此钱包仅用于大模型调用费用,不会自动充值交易所 • 私钥丢失后无法恢复 • 只充 Base 链 USDC' + : '• This wallet only covers LLM costs, not exchange funding • Private key cannot be recovered • Base USDC only'} +
+ + {/* Continue */}
- ) : null} -
-
+
+ ) : null} +
)