diff --git a/messages/en.json b/messages/en.json index 8769a61..b472350 100644 --- a/messages/en.json +++ b/messages/en.json @@ -141,7 +141,14 @@ "balance": "Balance", "showing": "Showing", "of": "of", - "transactions": "transactions" + "transactions": "transactions", + "topUp": "Top Up", + "enterAmount": "Enter amount to top up", + "quickAmounts": "Quick amounts", + "topUpNote": "Note: This is a development environment. No real payment will be processed.", + "processing": "Processing...", + "topUpNow": "Top Up Now", + "cancel": "Cancel" }, "studio": { "title": "AI Prompt Studio", diff --git a/messages/zh.json b/messages/zh.json index dbf150b..2b84855 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -141,7 +141,14 @@ "balance": "余额", "showing": "显示", "of": "的", - "transactions": "条记录" + "transactions": "条记录", + "topUp": "充值", + "enterAmount": "请输入充值金额", + "quickAmounts": "快捷金额", + "topUpNote": "注意:这是开发环境,不会进行真实的支付处理。", + "processing": "处理中...", + "topUpNow": "立即充值", + "cancel": "取消" }, "studio": { "title": "AI 提示词工作室", diff --git a/src/app/api/credits/topup/route.ts b/src/app/api/credits/topup/route.ts new file mode 100644 index 0000000..5103427 --- /dev/null +++ b/src/app/api/credits/topup/route.ts @@ -0,0 +1,71 @@ +import { NextResponse } from 'next/server' +import { createServerSupabaseClient } from '@/lib/supabase-server' +import { addCredit } from '@/lib/services/credit' + +export async function POST(request: Request) { + try { + const { amount } = await request.json() + + // 验证金额 + if (!amount || amount <= 0) { + return NextResponse.json( + { error: 'Invalid amount. Amount must be greater than 0.' }, + { status: 400 } + ) + } + + if (amount > 1000) { + return NextResponse.json( + { error: 'Amount too large. Maximum top-up amount is $1000.' }, + { status: 400 } + ) + } + + const supabase = await createServerSupabaseClient() + + // 获取当前用户 + const { data: { user }, error: authError } = await supabase.auth.getUser() + + if (authError || !user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // 检查是否是开发环境 + const isDevelopment = process.env.NODE_ENV === 'development' + + if (isDevelopment) { + // 开发环境:模拟充值成功 + console.log(`🧪 Development mode: Simulating top-up of $${amount} for user ${user.id}`) + + const creditTransaction = await addCredit( + user.id, + amount, + 'user_purchase', + 'development', + null, + null, + `Development top-up: $${amount}` + ) + + return NextResponse.json({ + success: true, + message: `Successfully topped up $${amount}`, + transaction: creditTransaction + }) + } else { + // 生产环境:集成真实的支付网关 + // 这里可以集成 Stripe, PayPal 等支付服务 + return NextResponse.json( + { error: 'Payment integration not implemented yet. Please contact support.' }, + { status: 501 } + ) + } + + } catch (error) { + console.error('Top-up failed:', error) + return NextResponse.json( + { error: 'Failed to process top-up' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/src/app/credits/page.tsx b/src/app/credits/page.tsx index be31787..940afcd 100644 --- a/src/app/credits/page.tsx +++ b/src/app/credits/page.tsx @@ -19,7 +19,9 @@ import { ChevronRight, Plus, Minus, - Zap + Zap, + DollarSign, + X } from 'lucide-react' interface CreditTransaction { @@ -65,6 +67,11 @@ export default function CreditsPage() { const [sortBy, setSortBy] = useState<'createdAt' | 'amount' | 'balance'>('createdAt') const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc') + // Top-up modal state + const [showTopUpModal, setShowTopUpModal] = useState(false) + const [topUpAmount, setTopUpAmount] = useState('') + const [isTopUpLoading, setIsTopUpLoading] = useState(false) + const totalPages = Math.ceil(total / pageSize) const loadCreditData = useCallback(async () => { @@ -173,6 +180,50 @@ export default function CreditsPage() { return new Date(dateString).toLocaleString() } + const handleTopUp = async () => { + const amount = parseFloat(topUpAmount) + + if (!amount || amount <= 0) { + alert('Please enter a valid amount greater than 0') + return + } + + if (amount > 1000) { + alert('Maximum top-up amount is $1000') + return + } + + setIsTopUpLoading(true) + try { + const response = await fetch('/api/credits/topup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ amount }), + }) + + const data = await response.json() + + if (response.ok) { + alert(`Successfully topped up $${amount}!`) + setShowTopUpModal(false) + setTopUpAmount('') + // Reload credit data to reflect the new balance + loadCreditData() + } else { + alert(data.error || 'Failed to process top-up') + } + } catch (error) { + console.error('Top-up error:', error) + alert('Failed to process top-up. Please try again.') + } finally { + setIsTopUpLoading(false) + } + } + + const predefinedAmounts = [5, 10, 25, 50, 100] + if (loading || !user) { return (
{t('currentBalance')}
++ ${stats.currentBalance.toFixed(2)} +
+{t('currentBalance')}
-- ${stats.currentBalance.toFixed(2)} -
-{t('totalEarned')}
+${stats.totalEarned.toFixed(2)}
+{t('totalEarned')}
-${stats.totalEarned.toFixed(2)}
-{t('totalEarned')}
+${stats.totalEarned.toFixed(2)}
+{t('totalSpent')}
-${stats.totalSpent.toFixed(2)}
-{t('totalSpent')}
+${stats.totalSpent.toFixed(2)}
+{t('thisMonthEarned')}
-${stats.thisMonthEarned.toFixed(2)}
-{t('thisMonthEarned')}
+${stats.thisMonthEarned.toFixed(2)}
+{t('thisMonthSpent')}
-${stats.thisMonthSpent.toFixed(2)}
+ + +{t('thisMonthSpent')}
+${stats.thisMonthSpent.toFixed(2)}
+{t('quickAmounts')}
++ {t('topUpNote')} +
+