add subscribe detail

This commit is contained in:
songtianlun 2025-08-26 22:20:34 +08:00
parent 79ddfc6898
commit 1d7fc6e5f2
4 changed files with 151 additions and 43 deletions

View File

@ -261,7 +261,7 @@
}, },
"subscription": { "subscription": {
"title": "Subscription Management", "title": "Subscription Management",
"subtitle": "Manage your subscription and billing", "subtitle": "Manage your subscription status",
"currentPlan": "Current Plan", "currentPlan": "Current Plan",
"planDetails": "Plan Details", "planDetails": "Plan Details",
"billingCycle": "Billing Cycle", "billingCycle": "Billing Cycle",
@ -286,7 +286,14 @@
"usage": "Usage", "usage": "Usage",
"promptsUsed": "Prompts Used", "promptsUsed": "Prompts Used",
"versionsUsed": "Versions Used", "versionsUsed": "Versions Used",
"unlimited": "Unlimited" "unlimited": "Unlimited",
"quickActions": "Quick Actions",
"manageBilling": "Manage Billing",
"viewAllPlans": "View All Plans",
"additionalOptions": "Additional Options",
"feeDetails": "Fee Details",
"feeDetailsDescription": "View transaction history and credit usage",
"planDetailsDescription": "View available subscription plans and features"
}, },
"admin": { "admin": {
"dashboard": "Admin Dashboard", "dashboard": "Admin Dashboard",

View File

@ -262,7 +262,7 @@
}, },
"subscription": { "subscription": {
"title": "订阅管理", "title": "订阅管理",
"subtitle": "管理您的订阅和账单", "subtitle": "管理您的订阅状态",
"currentPlan": "当前方案", "currentPlan": "当前方案",
"planDetails": "方案详情", "planDetails": "方案详情",
"billingCycle": "计费周期", "billingCycle": "计费周期",
@ -287,7 +287,14 @@
"usage": "使用情况", "usage": "使用情况",
"promptsUsed": "已使用提示词", "promptsUsed": "已使用提示词",
"versionsUsed": "已使用版本", "versionsUsed": "已使用版本",
"unlimited": "无限制" "unlimited": "无限制",
"quickActions": "快速操作",
"manageBilling": "管理账单",
"viewAllPlans": "查看所有方案",
"additionalOptions": "其他选项",
"feeDetails": "费用详情",
"feeDetailsDescription": "查看交易历史和信用使用记录",
"planDetailsDescription": "查看可用的订阅方案和功能特性"
}, },
"admin": { "admin": {
"dashboard": "管理员后台", "dashboard": "管理员后台",

View File

@ -0,0 +1,49 @@
import { NextResponse } from 'next/server'
import { createServerSupabaseClient } from '@/lib/supabase-server'
import { createCustomerPortalSession } from '@/lib/stripe'
export async function POST() {
try {
const supabase = await createServerSupabaseClient()
// 获取当前用户
const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// 获取用户的Stripe客户ID
const { data: userData, error: userError } = await supabase
.from('users')
.select('stripeCustomerId')
.eq('id', user.id)
.single()
if (userError || !userData?.stripeCustomerId) {
return NextResponse.json(
{ error: 'No Stripe customer found' },
{ status: 400 }
)
}
// 创建客户门户会话
const returnUrl = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/subscription`
const portalSession = await createCustomerPortalSession(
userData.stripeCustomerId,
returnUrl
)
return NextResponse.json({
url: portalSession.url
})
} catch (error) {
console.error('Portal session creation failed:', error)
return NextResponse.json(
{ error: 'Failed to create portal session' },
{ status: 500 }
)
}
}

View File

@ -139,6 +139,31 @@ export default function SubscriptionPage() {
} }
} }
const handleManageSubscription = async () => {
setActionLoading(true)
try {
const response = await fetch('/api/subscription/portal', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
if (response.ok) {
const result = await response.json()
// 重定向到Stripe客户门户
window.location.href = result.url
} else {
throw new Error('Failed to create portal session')
}
} catch (error) {
console.error('Portal creation failed:', error)
alert('Failed to access billing portal. Please try again.')
} finally {
setActionLoading(false)
}
}
if (loading || subscriptionLoading) { if (loading || subscriptionLoading) {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
@ -201,51 +226,71 @@ export default function SubscriptionPage() {
{/* Quick Actions */} {/* Quick Actions */}
<div className="bg-card rounded-lg border border-border p-6"> <div className="bg-card rounded-lg border border-border p-6">
<h3 className="font-semibold text-foreground mb-4">Quick Actions</h3> <h3 className="font-semibold text-foreground mb-4">{t('quickActions')}</h3>
<div className="flex flex-col sm:flex-row gap-3"> <div className="space-y-4">
{currentPlan === 'free' ? ( {/* Primary Actions */}
<QuickUpgradeButton className="flex items-center"> <div className="flex flex-col sm:flex-row gap-3">
<Crown className="w-4 h-4 mr-2" /> {currentPlan === 'free' ? (
{t('upgradePlan')} <QuickUpgradeButton className="flex items-center">
</QuickUpgradeButton> <Crown className="w-4 h-4 mr-2" />
) : ( {t('upgradePlan')}
<> </QuickUpgradeButton>
{/* 简单的账单信息显示 */} ) : (
<Button <>
variant="outline"
onClick={() => {
const message = currentPlan === 'pro'
? 'You are subscribed to the Pro plan ($19.9/month). To make changes to your subscription, please contact support.'
: 'You are on the Free plan. Upgrade to Pro for more features!'
alert(message)
}}
className="flex items-center"
>
<CreditCard className="w-4 h-4 mr-2" />
Billing Info
</Button>
{subscriptionData?.subscriptions && subscriptionData.subscriptions.length > 0 && (
<Button <Button
variant="destructive" variant="outline"
onClick={() => handleCancelSubscription(subscriptionData.subscriptions[0].id)} onClick={handleManageSubscription}
disabled={actionLoading} disabled={actionLoading}
className="flex items-center" className="flex items-center"
> >
{actionLoading ? <LoadingSpinner className="w-4 h-4 mr-2" /> : <AlertTriangle className="w-4 h-4 mr-2" />} {actionLoading ? <LoadingSpinner className="w-4 h-4 mr-2" /> : <CreditCard className="w-4 h-4 mr-2" />}
{t('cancelSubscription')} {t('manageBilling')}
</Button> </Button>
)} {subscriptionData?.subscriptions && subscriptionData.subscriptions.length > 0 && (
</> <Button
)} variant="destructive"
onClick={() => handleCancelSubscription(subscriptionData.subscriptions[0].id)}
disabled={actionLoading}
className="flex items-center"
>
{actionLoading ? <LoadingSpinner className="w-4 h-4 mr-2" /> : <AlertTriangle className="w-4 h-4 mr-2" />}
{t('cancelSubscription')}
</Button>
)}
</>
)}
</div>
<Button {/* Secondary Actions */}
variant="outline" <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
onClick={() => router.push('/pricing')} <Button
className="flex items-center" variant="ghost"
> onClick={() => router.push('/pricing')}
<Star className="w-4 h-4 mr-2" /> className="flex items-center justify-start p-4 h-auto"
View All Plans >
</Button> <div className="flex flex-col items-start text-left">
<div className="flex items-center mb-1">
<Crown className="w-4 h-4 mr-2" />
<span className="font-medium">{t('planDetails')}</span>
</div>
<span className="text-sm text-muted-foreground">{t('planDetailsDescription')}</span>
</div>
</Button>
<Button
variant="ghost"
onClick={() => router.push('/credits')}
className="flex items-center justify-start p-4 h-auto"
>
<div className="flex flex-col items-start text-left">
<div className="flex items-center mb-1">
<CreditCard className="w-4 h-4 mr-2" />
<span className="font-medium">{t('feeDetails')}</span>
</div>
<span className="text-sm text-muted-foreground">{t('feeDetailsDescription')}</span>
</div>
</Button>
</div>
</div> </div>
</div> </div>
</div> </div>