From 5b3674804876d65f99399b07df0cb86ac7543dfe Mon Sep 17 00:00:00 2001 From: songtianlun Date: Wed, 6 Aug 2025 22:21:45 +0800 Subject: [PATCH] fix build --- docs/user-cache-optimization.md | 15 +++++++ src/app/api/webhooks/stripe/route.ts | 4 +- src/app/pricing/page.tsx | 43 ++++++++++++------- src/app/profile/page.tsx | 2 +- src/app/subscription/page.tsx | 35 +-------------- src/components/plaza/PromptCard.tsx | 4 +- src/components/plaza/PromptDetailModal.tsx | 4 +- .../subscription/SubscribeButton.tsx | 2 +- src/hooks/useAuthUser.ts | 2 +- src/lib/subscription-utils.ts | 14 +++--- src/lib/user-cache.ts | 4 +- 11 files changed, 60 insertions(+), 69 deletions(-) diff --git a/docs/user-cache-optimization.md b/docs/user-cache-optimization.md index 7ad94e8..13b1149 100644 --- a/docs/user-cache-optimization.md +++ b/docs/user-cache-optimization.md @@ -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. **用户反馈**:收集用户对页面加载速度改善的反馈 diff --git a/src/app/api/webhooks/stripe/route.ts b/src/app/api/webhooks/stripe/route.ts index ae35255..d17e667 100644 --- a/src/app/api/webhooks/stripe/route.ts +++ b/src/app/api/webhooks/stripe/route.ts @@ -48,7 +48,7 @@ export async function POST(request: NextRequest) { break case 'invoice.payment_failed': - await handlePaymentFailed(event.data.object as unknown as Record) + await handlePaymentFailed() break default: @@ -277,7 +277,7 @@ async function handlePaymentSucceeded(invoice: Record) { } } -async function handlePaymentFailed(_invoice: Record) { +async function handlePaymentFailed() { try { // 这里可以添加额外的逻辑,比如发送提醒邮件等 diff --git a/src/app/pricing/page.tsx b/src/app/pricing/page.tsx index ee2b0e4..962d8fc 100644 --- a/src/app/pricing/page.tsx +++ b/src/app/pricing/page.tsx @@ -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([]) + const [plans, setPlans] = useState>([]) 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() {

- {plan.displayName} + {plan.displayName || plan.name}

{formatPlanPrice(plan, t)} @@ -217,7 +228,7 @@ export default function PricingPage() { return ( {t('subscribeNow')} diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index 69646ae..5b15938 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -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' diff --git a/src/app/subscription/page.tsx b/src/app/subscription/page.tsx index 19df9fe..ba5c184 100644 --- a/src/app/subscription/page.tsx +++ b/src/app/subscription/page.tsx @@ -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) diff --git a/src/components/plaza/PromptCard.tsx b/src/components/plaza/PromptCard.tsx index 404345f..a348b99 100644 --- a/src/components/plaza/PromptCard.tsx +++ b/src/components/plaza/PromptCard.tsx @@ -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' diff --git a/src/components/plaza/PromptDetailModal.tsx b/src/components/plaza/PromptDetailModal.tsx index c50a981..5cc2186 100644 --- a/src/components/plaza/PromptDetailModal.tsx +++ b/src/components/plaza/PromptDetailModal.tsx @@ -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' diff --git a/src/components/subscription/SubscribeButton.tsx b/src/components/subscription/SubscribeButton.tsx index e4021e0..b927fd4 100644 --- a/src/components/subscription/SubscribeButton.tsx +++ b/src/components/subscription/SubscribeButton.tsx @@ -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) diff --git a/src/hooks/useAuthUser.ts b/src/hooks/useAuthUser.ts index 3dfe907..9ee820b 100644 --- a/src/hooks/useAuthUser.ts +++ b/src/hooks/useAuthUser.ts @@ -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), ]) diff --git a/src/lib/subscription-utils.ts b/src/lib/subscription-utils.ts index f63cb53..c44b12c 100644 --- a/src/lib/subscription-utils.ts +++ b/src/lib/subscription-utils.ts @@ -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 + const limits = (plan.limits as Record) || {} 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 + const features = (plan.features as Record) || {} 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) } diff --git a/src/lib/user-cache.ts b/src/lib/user-cache.ts index cb9e22b..c56ee99 100644 --- a/src/lib/user-cache.ts +++ b/src/lib/user-cache.ts @@ -37,7 +37,7 @@ const CACHE_CONFIG = { class UserCache { private userDataCache = new Map>() private syncTimestamps = new Map() - private syncPromises = new Map>() + private syncPromises = new Map>() // 获取缓存的用户数据 getUserData(userId: string): UserData | null { @@ -85,7 +85,7 @@ class UserCache { } // 获取或创建同步Promise(防止重复同步) - getSyncPromise(userId: string, syncFn: () => Promise): Promise { + getSyncPromise(userId: string, syncFn: () => Promise): Promise { const existing = this.syncPromises.get(userId) if (existing) return existing