fix build
This commit is contained in:
parent
0681738a27
commit
5b36748048
@ -166,3 +166,18 @@ import { useUser } from '@/hooks/useUser'
|
||||
4. **错误率**:监控缓存相关的错误
|
||||
|
||||
这个优化显著改善了应用的性能和用户体验,同时降低了服务器负载和运营成本。
|
||||
|
||||
## 构建状态
|
||||
|
||||
✅ **构建成功** - 所有 TypeScript 错误已修复,应用可以正常构建和部署。
|
||||
|
||||
剩余的警告(非关键):
|
||||
- Google Analytics 脚本建议使用 `next/script` 组件
|
||||
- Avatar 组件建议使用 `next/image` 优化图片加载
|
||||
|
||||
## 部署建议
|
||||
|
||||
1. **生产环境验证**:在生产环境中监控 API 调用频率和缓存命中率
|
||||
2. **性能监控**:使用 `/debug/cache` 页面定期检查缓存性能
|
||||
3. **错误监控**:关注缓存相关的错误日志
|
||||
4. **用户反馈**:收集用户对页面加载速度改善的反馈
|
||||
|
@ -48,7 +48,7 @@ export async function POST(request: NextRequest) {
|
||||
break
|
||||
|
||||
case 'invoice.payment_failed':
|
||||
await handlePaymentFailed(event.data.object as unknown as Record<string, unknown>)
|
||||
await handlePaymentFailed()
|
||||
break
|
||||
|
||||
default:
|
||||
@ -277,7 +277,7 @@ async function handlePaymentSucceeded(invoice: Record<string, unknown>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePaymentFailed(_invoice: Record<string, unknown>) {
|
||||
async function handlePaymentFailed() {
|
||||
try {
|
||||
// 这里可以添加额外的逻辑,比如发送提醒邮件等
|
||||
|
||||
|
@ -6,8 +6,8 @@ import { Header } from '@/components/layout/Header'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Check, Crown, Star } from 'lucide-react'
|
||||
import { SubscribeButton } from '@/components/subscription/SubscribeButton'
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { SubscriptionPlan } from '@prisma/client'
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
// Remove unused import
|
||||
import {
|
||||
isPlanPro,
|
||||
isPlanFree,
|
||||
@ -20,16 +20,32 @@ import {
|
||||
export default function PricingPage() {
|
||||
const { user, userData } = useAuthUser()
|
||||
const t = useTranslations('pricing')
|
||||
const [plans, setPlans] = useState<any[]>([])
|
||||
const [plans, setPlans] = useState<Array<{
|
||||
id: string
|
||||
name: string
|
||||
displayName?: string
|
||||
description: string
|
||||
price: number
|
||||
currency: string
|
||||
interval: string
|
||||
features: string[]
|
||||
stripePriceId: string
|
||||
isPopular?: boolean
|
||||
}>>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const fetchPlans = async () => {
|
||||
const isCurrentPlan = useCallback((planId: string) => {
|
||||
if (!userData) return false
|
||||
return userData.subscriptionPlanId === planId
|
||||
}, [userData])
|
||||
|
||||
const fetchPlans = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/subscription-plans')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
// 过滤套餐:只显示真正的免费套餐、用户当前套餐,以及有 stripePriceId 的付费套餐
|
||||
const filteredPlans = (data.plans || []).filter((plan: SubscriptionPlan) => {
|
||||
const filteredPlans = (data.plans || []).filter((plan: { id: string; name: string; price: number; stripePriceId?: string }) => {
|
||||
// 只显示官方的免费套餐(ID为'free'或名称为'free')
|
||||
if (isPlanFree(plan) && (plan.id === 'free' || plan.name.toLowerCase() === 'free')) {
|
||||
return true
|
||||
@ -52,18 +68,13 @@ export default function PricingPage() {
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, [userData, isCurrentPlan])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchPlans()
|
||||
}, [userData]) // 依赖 userData,确保用户数据加载后再过滤套餐
|
||||
|
||||
|
||||
|
||||
const isCurrentPlan = (planId: string) => {
|
||||
if (!userData) return false
|
||||
return userData.subscriptionPlanId === planId
|
||||
}
|
||||
}, [fetchPlans]) // 依赖 fetchPlans
|
||||
|
||||
|
||||
|
||||
@ -136,7 +147,7 @@ export default function PricingPage() {
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-card-foreground mb-2">
|
||||
{plan.displayName}
|
||||
{plan.displayName || plan.name}
|
||||
</h3>
|
||||
<div className="text-4xl font-bold text-card-foreground mb-2">
|
||||
{formatPlanPrice(plan, t)}
|
||||
@ -217,7 +228,7 @@ export default function PricingPage() {
|
||||
return (
|
||||
<SubscribeButton
|
||||
priceId={plan.stripePriceId}
|
||||
planName={plan.displayName}
|
||||
planName={plan.displayName || plan.name}
|
||||
className={`w-full ${isPro ? 'bg-primary hover:bg-primary/90' : ''}`}
|
||||
>
|
||||
{t('subscribeNow')}
|
||||
|
@ -10,7 +10,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { LegacyAvatar } from '@/components/ui/avatar'
|
||||
import { LoadingSpinner, LoadingOverlay, GradientLoading } from '@/components/ui/loading-spinner'
|
||||
import { LoadingSpinner, LoadingOverlay } from '@/components/ui/loading-spinner'
|
||||
import { AvatarSkeleton, FormFieldSkeleton, TextAreaSkeleton } from '@/components/ui/skeleton'
|
||||
import { Save, Eye, EyeOff, CreditCard, Crown, Star } from 'lucide-react'
|
||||
|
||||
|
@ -24,7 +24,7 @@ interface SubscriptionData {
|
||||
}
|
||||
|
||||
export default function SubscriptionPage() {
|
||||
const { user, userData, loading, triggerSubscriptionUpdate } = useAuthUser()
|
||||
const { user, userData, loading } = useAuthUser()
|
||||
const router = useRouter()
|
||||
const t = useTranslations('subscription')
|
||||
|
||||
@ -111,40 +111,7 @@ export default function SubscriptionPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleManageSubscription = async () => {
|
||||
setActionLoading(true)
|
||||
try {
|
||||
const response = await fetch('/api/subscription/manage', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'portal'
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const { url } = await response.json()
|
||||
window.location.href = url
|
||||
} else {
|
||||
const errorData = await response.json()
|
||||
console.error('Portal error:', errorData)
|
||||
|
||||
// 如果是客户门户未配置的错误,提供替代方案
|
||||
if (errorData.details?.includes('billing portal') || errorData.details?.includes('portal')) {
|
||||
alert('Billing portal is not yet configured. Please contact support for subscription management.')
|
||||
} else {
|
||||
throw new Error(errorData.error || 'Failed to create portal session')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Portal failed:', error)
|
||||
alert('Failed to open billing portal. Please try again.')
|
||||
} finally {
|
||||
setActionLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSyncSubscription = async () => {
|
||||
setActionLoading(true)
|
||||
|
@ -126,7 +126,7 @@ export function PromptCard({ prompt, onViewIncrement }: PromptCardProps) {
|
||||
try {
|
||||
const errorData = await response.json()
|
||||
errorMessage = errorData.error || `HTTP ${response.status}: ${response.statusText}`
|
||||
} catch (e) {
|
||||
} catch {
|
||||
errorMessage = `HTTP ${response.status}: ${response.statusText}`
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ export function PromptCard({ prompt, onViewIncrement }: PromptCardProps) {
|
||||
}, 300)
|
||||
}, 3000)
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Create error notification for network/other errors
|
||||
const notification = document.createElement('div')
|
||||
notification.className = 'fixed top-4 right-4 bg-red-500 text-white px-4 py-2 rounded-md shadow-lg z-50 transition-all duration-300'
|
||||
|
@ -128,7 +128,7 @@ export function PromptDetailModal({
|
||||
try {
|
||||
const errorData = await response.json()
|
||||
errorMessage = errorData.error || `HTTP ${response.status}: ${response.statusText}`
|
||||
} catch (e) {
|
||||
} catch {
|
||||
errorMessage = `HTTP ${response.status}: ${response.statusText}`
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ export function PromptDetailModal({
|
||||
}, 300)
|
||||
}, 3000)
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Create error notification for network/other errors
|
||||
const notification = document.createElement('div')
|
||||
notification.className = 'fixed top-4 right-4 bg-red-500 text-white px-4 py-2 rounded-md shadow-lg z-50 transition-all duration-300'
|
||||
|
@ -116,7 +116,7 @@ export function QuickUpgradeButton({ className, children }: QuickUpgradeButtonPr
|
||||
} else {
|
||||
setError('Failed to load Pro plan')
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError('Failed to load Pro plan')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
@ -122,7 +122,7 @@ export function useAuthUser() {
|
||||
const syncOptions: SyncOptions = { trigger }
|
||||
|
||||
// 并行执行同步和获取用户数据
|
||||
const [, userData] = await Promise.all([
|
||||
await Promise.all([
|
||||
syncUserToDatabase(user.id, syncOptions),
|
||||
fetchUserData(user.id, syncOptions),
|
||||
])
|
||||
|
@ -1,11 +1,9 @@
|
||||
import type { SubscriptionPlan } from '@prisma/client'
|
||||
|
||||
/**
|
||||
* 订阅相关的工具函数
|
||||
*/
|
||||
|
||||
// 支持部分套餐数据的类型
|
||||
type PlanLike = { price: number } | SubscriptionPlan | null | undefined
|
||||
type PlanLike = { price: number; id?: string; name?: string; features?: unknown; limits?: unknown } | null | undefined
|
||||
|
||||
/**
|
||||
* 判断套餐是否为 Pro 级别(价格超过 19)
|
||||
@ -104,7 +102,7 @@ export function formatPlanPrice(plan: PlanLike & { interval?: string }, t?: (key
|
||||
/**
|
||||
* 获取套餐限制的类型安全访问器
|
||||
*/
|
||||
export function getPlanLimits(plan: SubscriptionPlan | null | undefined): {
|
||||
export function getPlanLimits(plan: PlanLike): {
|
||||
promptLimit: number
|
||||
maxVersionLimit: number
|
||||
creditMonthly: number
|
||||
@ -117,7 +115,7 @@ export function getPlanLimits(plan: SubscriptionPlan | null | undefined): {
|
||||
}
|
||||
}
|
||||
|
||||
const limits = plan.limits as Record<string, unknown>
|
||||
const limits = (plan.limits as Record<string, unknown>) || {}
|
||||
return {
|
||||
promptLimit: (limits?.promptLimit as number) || 500,
|
||||
maxVersionLimit: (limits?.maxVersionLimit as number) || 3,
|
||||
@ -128,17 +126,17 @@ export function getPlanLimits(plan: SubscriptionPlan | null | undefined): {
|
||||
/**
|
||||
* 获取套餐功能的类型安全访问器
|
||||
*/
|
||||
export function getPlanFeatures(plan: SubscriptionPlan | null | undefined): string[] {
|
||||
export function getPlanFeatures(plan: PlanLike): string[] {
|
||||
if (!plan) return []
|
||||
|
||||
const features = plan.features as Record<string, unknown>
|
||||
const features = (plan.features as Record<string, unknown>) || {}
|
||||
return Object.keys(features).filter(key => features[key] === true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查套餐是否包含特定功能
|
||||
*/
|
||||
export function planHasFeature(plan: SubscriptionPlan | null | undefined, feature: string): boolean {
|
||||
export function planHasFeature(plan: PlanLike, feature: string): boolean {
|
||||
const features = getPlanFeatures(plan)
|
||||
return features.includes(feature)
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ const CACHE_CONFIG = {
|
||||
class UserCache {
|
||||
private userDataCache = new Map<string, CacheItem<UserData>>()
|
||||
private syncTimestamps = new Map<string, number>()
|
||||
private syncPromises = new Map<string, Promise<any>>()
|
||||
private syncPromises = new Map<string, Promise<void>>()
|
||||
|
||||
// 获取缓存的用户数据
|
||||
getUserData(userId: string): UserData | null {
|
||||
@ -85,7 +85,7 @@ class UserCache {
|
||||
}
|
||||
|
||||
// 获取或创建同步Promise(防止重复同步)
|
||||
getSyncPromise(userId: string, syncFn: () => Promise<any>): Promise<any> {
|
||||
getSyncPromise(userId: string, syncFn: () => Promise<void>): Promise<void> {
|
||||
const existing = this.syncPromises.get(userId)
|
||||
if (existing) return existing
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user