refactor: enhance credit balance and stats hooks with improved error handling and logging

This commit is contained in:
javayhu 2025-08-21 01:48:40 +08:00
parent 13c23dab56
commit 1fb89a2a05
4 changed files with 68 additions and 56 deletions

View File

@ -15,8 +15,8 @@ export function QueryProvider({ children }: QueryProviderProps) {
new QueryClient({ new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
staleTime: 5 * 60 * 1000, // 5 minutes staleTime: 5 * 60 * 1000, // 5 minutes - default stale time
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime) gcTime: 10 * 60 * 1000, // 10 minutes - default garbage collection time
retry: 1, retry: 1,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}, },

View File

@ -11,7 +11,11 @@ import {
} from '@/components/ui/card'; } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import { websiteConfig } from '@/config/website'; import { websiteConfig } from '@/config/website';
import { useCreditBalance, useCreditStats } from '@/hooks/use-credits'; import {
creditsKeys,
useCreditBalance,
useCreditStats,
} from '@/hooks/use-credits';
import { useMounted } from '@/hooks/use-mounted'; import { useMounted } from '@/hooks/use-mounted';
import { useCurrentPlan } from '@/hooks/use-payment'; import { useCurrentPlan } from '@/hooks/use-payment';
import { useLocaleRouter } from '@/i18n/navigation'; import { useLocaleRouter } from '@/i18n/navigation';
@ -19,6 +23,7 @@ import { authClient } from '@/lib/auth-client';
import { formatDate } from '@/lib/formatter'; import { formatDate } from '@/lib/formatter';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Routes } from '@/routes'; import { Routes } from '@/routes';
import { useQueryClient } from '@tanstack/react-query';
import { RefreshCwIcon } from 'lucide-react'; import { RefreshCwIcon } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
@ -37,6 +42,7 @@ export default function CreditsBalanceCard() {
const t = useTranslations('Dashboard.settings.credits.balance'); const t = useTranslations('Dashboard.settings.credits.balance');
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const localeRouter = useLocaleRouter(); const localeRouter = useLocaleRouter();
const queryClient = useQueryClient();
const hasHandledSession = useRef(false); const hasHandledSession = useRef(false);
const mounted = useMounted(); const mounted = useMounted();
@ -44,8 +50,8 @@ export default function CreditsBalanceCard() {
const { const {
data: balance = 0, data: balance = 0,
isLoading: isLoadingBalance, isLoading: isLoadingBalance,
error, error: balanceError,
refetch: refetchCredits, refetch: refetchBalance,
} = useCreditBalance(); } = useCreditBalance();
// Get payment info to check plan type // Get payment info to check plan type
@ -58,7 +64,7 @@ export default function CreditsBalanceCard() {
data: creditStats, data: creditStats,
isLoading: isLoadingStats, isLoading: isLoadingStats,
error: statsError, error: statsError,
refetch: refetchCreditStats, refetch: refetchStats,
} = useCreditStats(); } = useCreditStats();
// Check for payment success and show success message // Check for payment success and show success message
@ -67,35 +73,48 @@ export default function CreditsBalanceCard() {
if (sessionId && !hasHandledSession.current) { if (sessionId && !hasHandledSession.current) {
hasHandledSession.current = true; hasHandledSession.current = true;
setTimeout(() => { // Clean up URL parameters first
// Show success toast and refresh data after payment
toast.success(t('creditsAdded'));
// Force refresh credits data to show updated balance
refetchCredits();
// Refresh credit stats
refetchCreditStats();
}, 0);
// Clean up URL parameters
const url = new URL(window.location.href); const url = new URL(window.location.href);
url.searchParams.delete('credits_session_id'); url.searchParams.delete('credits_session_id');
localeRouter.replace(Routes.SettingsCredits + url.search); localeRouter.replace(Routes.SettingsCredits + url.search);
// Handle payment success with proper timing
const handlePaymentSuccess = async () => {
// Show success toast (must be in setTimeout to avoid errors)
setTimeout(() => {
toast.success(t('creditsAdded'));
}, 0);
// Wait for webhook to process
await new Promise((resolve) => setTimeout(resolve, 1000));
// Force refresh data
queryClient.invalidateQueries({
queryKey: creditsKeys.balance(),
});
queryClient.invalidateQueries({
queryKey: creditsKeys.stats(),
});
};
handlePaymentSuccess();
} }
}, [searchParams, localeRouter, refetchCredits, refetchCreditStats, t]); }, [searchParams, localeRouter, queryClient, t]);
// Retry all data fetching // Retry all data fetching
const handleRetry = useCallback(() => { const handleRetry = useCallback(() => {
// console.log('handleRetry, refetch credits data'); // console.log('handleRetry, refetch credits data');
// Force refresh credits balance (ignore cache) // Force invalidate cache to ensure fresh data
refetchCredits(); queryClient.invalidateQueries({
// Refresh credit stats queryKey: creditsKeys.balance(),
refetchCreditStats(); });
}, [refetchCredits, refetchCreditStats]); queryClient.invalidateQueries({
queryKey: creditsKeys.stats(),
});
}, [queryClient]);
// Render loading skeleton // Render loading skeleton
const isPageLoading = isLoadingBalance || isLoadingStats; if (!mounted || isLoadingBalance || isLoadingStats) {
if (!mounted || isPageLoading) {
return ( return (
<Card className={cn('w-full overflow-hidden pt-6 pb-0 flex flex-col')}> <Card className={cn('w-full overflow-hidden pt-6 pb-0 flex flex-col')}>
<CardHeader> <CardHeader>
@ -116,7 +135,7 @@ export default function CreditsBalanceCard() {
} }
// Render error state // Render error state
if (error || statsError) { if (balanceError || statsError) {
return ( return (
<Card className={cn('w-full overflow-hidden pt-6 pb-0 flex flex-col')}> <Card className={cn('w-full overflow-hidden pt-6 pb-0 flex flex-col')}>
<CardHeader> <CardHeader>
@ -125,7 +144,7 @@ export default function CreditsBalanceCard() {
</CardHeader> </CardHeader>
<CardContent className="space-y-4 flex-1"> <CardContent className="space-y-4 flex-1">
<div className="text-destructive text-sm"> <div className="text-destructive text-sm">
{error?.message || statsError?.message} {balanceError?.message || statsError?.message}
</div> </div>
</CardContent> </CardContent>
<CardFooter className="mt-2 px-6 py-4 flex justify-end items-center bg-background rounded-none"> <CardFooter className="mt-2 px-6 py-4 flex justify-end items-center bg-background rounded-none">

View File

@ -19,31 +19,39 @@ export const creditsKeys = {
}) => [...creditsKeys.transactions(), filters] as const, }) => [...creditsKeys.transactions(), filters] as const,
}; };
// Hook to fetch credit statistics
export function useCreditStats() {
return useQuery({
queryKey: creditsKeys.stats(),
queryFn: async () => {
const result = await getCreditStatsAction();
if (!result?.data?.success) {
throw new Error(result?.data?.error || 'Failed to fetch credit stats');
}
return result.data.data;
},
});
}
// Hook to fetch credit balance // Hook to fetch credit balance
export function useCreditBalance() { export function useCreditBalance() {
return useQuery({ return useQuery({
queryKey: creditsKeys.balance(), queryKey: creditsKeys.balance(),
queryFn: async () => { queryFn: async () => {
console.log('Fetching credit balance...');
const result = await getCreditBalanceAction(); const result = await getCreditBalanceAction();
if (!result?.data?.success) { if (!result?.data?.success) {
throw new Error('Failed to fetch credit balance'); throw new Error('Failed to fetch credit balance');
} }
console.log('Credit balance fetched:', result.data.credits);
return result.data.credits || 0; return result.data.credits || 0;
}, },
staleTime: 30 * 1000, // 30 seconds - reasonable stale time
retry: 2, // Retry up to 2 times on failure
});
}
// Hook to fetch credit statistics
export function useCreditStats() {
return useQuery({
queryKey: creditsKeys.stats(),
queryFn: async () => {
console.log('Fetching credit stats...');
const result = await getCreditStatsAction();
if (!result?.data?.success) {
throw new Error(result?.data?.error || 'Failed to fetch credit stats');
}
console.log('Credit stats fetched:', result.data.data);
return result.data.data;
},
staleTime: 30 * 1000, // 30 seconds - reasonable stale time
retry: 2, // Retry up to 2 times on failure
}); });
} }

View File

@ -1,15 +0,0 @@
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
retry: 1,
refetchOnWindowFocus: false,
},
mutations: {
retry: 1,
},
},
});