From 0d04f6914ec109bacf404e97c6c8c883da27c4ab Mon Sep 17 00:00:00 2001 From: javayhu Date: Thu, 24 Jul 2025 00:50:23 +0800 Subject: [PATCH 1/3] refactor: optimize credits rendering by memoizing and moving checks before hooks --- .../(protected)/settings/billing/page.tsx | 8 ++++++-- .../settings/billing/credits-balance-card.tsx | 10 +++++----- .../settings/credits/credit-packages.tsx | 15 ++++++++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/app/[locale]/(protected)/settings/billing/page.tsx b/src/app/[locale]/(protected)/settings/billing/page.tsx index dddba6b..1246b71 100644 --- a/src/app/[locale]/(protected)/settings/billing/page.tsx +++ b/src/app/[locale]/(protected)/settings/billing/page.tsx @@ -2,18 +2,22 @@ import BillingCard from '@/components/settings/billing/billing-card'; import CreditsBalanceCard from '@/components/settings/billing/credits-balance-card'; import { CreditPackages } from '@/components/settings/credits/credit-packages'; import { websiteConfig } from '@/config/website'; +import { useMemo } from 'react'; export default function BillingPage() { + // Memoize the credits enabled state to ensure consistency across renders + const creditsEnabled = useMemo(() => websiteConfig.credits.enableCredits, []); + return (
{/* Billing and Credits Balance Cards */}
- {websiteConfig.credits.enableCredits && } + {creditsEnabled && }
{/* Credit Packages */} - {websiteConfig.credits.enableCredits && ( + {creditsEnabled && (
diff --git a/src/components/settings/billing/credits-balance-card.tsx b/src/components/settings/billing/credits-balance-card.tsx index bddc3ee..7db64f8 100644 --- a/src/components/settings/billing/credits-balance-card.tsx +++ b/src/components/settings/billing/credits-balance-card.tsx @@ -26,6 +26,11 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'sonner'; export default function CreditsBalanceCard() { + // Don't render if credits are disabled - move this check before any hooks + if (!websiteConfig.credits.enableCredits) { + return null; + } + const t = useTranslations('Dashboard.settings.credits.balance'); const searchParams = useSearchParams(); const localeRouter = useLocaleRouter(); @@ -109,11 +114,6 @@ export default function CreditsBalanceCard() { fetchCreditStats(); }, [fetchCredits, fetchCreditStats]); - // Don't render if credits are disabled - if (!websiteConfig.credits.enableCredits) { - return null; - } - // Render loading skeleton const isPageLoading = isLoadingBalance || isLoadingStats; if (!mounted || isPageLoading) { diff --git a/src/components/settings/credits/credit-packages.tsx b/src/components/settings/credits/credit-packages.tsx index 9a2945a..2e93b54 100644 --- a/src/components/settings/credits/credit-packages.tsx +++ b/src/components/settings/credits/credit-packages.tsx @@ -23,12 +23,22 @@ import { CreditCheckoutButton } from './credit-checkout-button'; * @returns Credit packages component */ export function CreditPackages() { + // Check if credits are enabled - move this check before any hooks + if (!websiteConfig.credits.enableCredits) { + return null; + } + const t = useTranslations('Dashboard.settings.credits.packages'); // Get current user and payment info const currentUser = useCurrentUser(); const { currentPlan } = usePayment(); + // Get credit packages with translations - must be called here to maintain hook order + const creditPackages = Object.values(getCreditPackages()).filter( + (pkg) => !pkg.disabled && pkg.price.priceId + ); + // Check if user is on free plan and enableForFreePlan is false const isFreePlan = currentPlan?.isFree === true; @@ -37,11 +47,6 @@ export function CreditPackages() { return null; } - // show only enabled packages - const creditPackages = Object.values(getCreditPackages()).filter( - (pkg) => !pkg.disabled && pkg.price.priceId - ); - return ( From 3cb0911cf4517ba15296ccddc6bacefc9bc0c30e Mon Sep 17 00:00:00 2001 From: javayhu Date: Thu, 24 Jul 2025 01:14:53 +0800 Subject: [PATCH 2/3] fix: ensure current user is only displayed when component is mounted --- src/components/pricing/pricing-card.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/pricing/pricing-card.tsx b/src/components/pricing/pricing-card.tsx index e526b0f..5d02242 100644 --- a/src/components/pricing/pricing-card.tsx +++ b/src/components/pricing/pricing-card.tsx @@ -9,6 +9,7 @@ import { CardTitle, } from '@/components/ui/card'; import { useCurrentUser } from '@/hooks/use-current-user'; +import { useMounted } from '@/hooks/use-mounted'; import { useLocalePathname } from '@/i18n/navigation'; import { formatPrice } from '@/lib/formatter'; import { cn } from '@/lib/utils'; @@ -79,6 +80,7 @@ export function PricingCard({ const price = getPriceForPlan(plan, interval, paymentType); const currentUser = useCurrentUser(); const currentPath = useLocalePathname(); + const mounted = useMounted(); // console.log('pricing card, currentPath', currentPath); // generate formatted price and price label @@ -152,7 +154,7 @@ export function PricingCard({ {/* show action buttons based on plans */} {plan.isFree ? ( - currentUser ? ( + mounted && currentUser ? ( @@ -172,7 +174,7 @@ export function PricingCard({ {t('yourCurrentPlan')} ) : isPaidPlan ? ( - currentUser ? ( + mounted && currentUser ? ( Date: Thu, 24 Jul 2025 01:16:55 +0800 Subject: [PATCH 3/3] feat: add credits configuration checks in hooks and provider --- src/hooks/use-credits.ts | 14 ++++++++++++++ src/providers/credits-provider.tsx | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/src/hooks/use-credits.ts b/src/hooks/use-credits.ts index f3837c6..2cfd0ee 100644 --- a/src/hooks/use-credits.ts +++ b/src/hooks/use-credits.ts @@ -1,3 +1,4 @@ +import { websiteConfig } from '@/config/website'; import { authClient } from '@/lib/auth-client'; import { useCreditsStore } from '@/stores/credits-store'; import { useCallback, useEffect } from 'react'; @@ -7,8 +8,21 @@ import { useCallback, useEffect } from 'react'; * * This hook provides access to the credits state and methods to manage it. * It also automatically fetches credits information when the user changes. + * Only works when credits are enabled in the website configuration. */ export function useCredits() { + // Return default values if credits are disabled + if (!websiteConfig.credits.enableCredits) { + return { + balance: 0, + isLoading: false, + error: null, + fetchCredits: () => Promise.resolve(), + consumeCredits: () => Promise.resolve(false), + hasEnoughCredits: () => false, + }; + } + const { balance, isLoading, diff --git a/src/providers/credits-provider.tsx b/src/providers/credits-provider.tsx index 32ec079..5fbed6e 100644 --- a/src/providers/credits-provider.tsx +++ b/src/providers/credits-provider.tsx @@ -1,5 +1,6 @@ 'use client'; +import { websiteConfig } from '@/config/website'; import { authClient } from '@/lib/auth-client'; import { useCreditsStore } from '@/stores/credits-store'; import { useEffect } from 'react'; @@ -9,8 +10,14 @@ import { useEffect } from 'react'; * * This component initializes the credits store when the user is authenticated * and handles cleanup when the user logs out. + * Only renders when credits are enabled in the website configuration. */ export function CreditsProvider({ children }: { children: React.ReactNode }) { + // Only initialize credits store if credits are enabled + if (!websiteConfig.credits.enableCredits) { + return <>{children}; + } + const { fetchCredits } = useCreditsStore(); const { data: session } = authClient.useSession();