refactor: replace useCredits hook with custom hooks for credit balance, consumption, and transactions management
This commit is contained in:
parent
d153ca655e
commit
ff1e72df13
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { CreditsBalanceButton } from '@/components/layout/credits-balance-button';
|
import { CreditsBalanceButton } from '@/components/layout/credits-balance-button';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useCredits } from '@/hooks/use-credits';
|
import { useConsumeCredits, useCreditBalance } from '@/hooks/use-credits-query';
|
||||||
import { CoinsIcon } from 'lucide-react';
|
import { CoinsIcon } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@ -10,24 +10,28 @@ import { toast } from 'sonner';
|
|||||||
const CONSUME_CREDITS = 50;
|
const CONSUME_CREDITS = 50;
|
||||||
|
|
||||||
export function ConsumeCreditCard() {
|
export function ConsumeCreditCard() {
|
||||||
const { consumeCredits, hasEnoughCredits, isLoading } = useCredits();
|
const { data: balance = 0, isLoading: isLoadingBalance } = useCreditBalance();
|
||||||
|
const consumeCreditsMutation = useConsumeCredits();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const hasEnoughCredits = (amount: number) => balance >= amount;
|
||||||
|
|
||||||
const handleConsume = async () => {
|
const handleConsume = async () => {
|
||||||
if (!hasEnoughCredits(CONSUME_CREDITS)) {
|
if (!hasEnoughCredits(CONSUME_CREDITS)) {
|
||||||
toast.error('Insufficient credits, please buy more credits.');
|
toast.error('Insufficient credits, please buy more credits.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const success = await consumeCredits(
|
try {
|
||||||
CONSUME_CREDITS,
|
await consumeCreditsMutation.mutateAsync({
|
||||||
`AI Text Credit Consumption (${CONSUME_CREDITS} credits)`
|
amount: CONSUME_CREDITS,
|
||||||
);
|
description: `AI Text Credit Consumption (${CONSUME_CREDITS} credits)`,
|
||||||
setLoading(false);
|
});
|
||||||
if (success) {
|
|
||||||
toast.success(`${CONSUME_CREDITS} credits have been consumed.`);
|
toast.success(`${CONSUME_CREDITS} credits have been consumed.`);
|
||||||
} else {
|
} catch (error) {
|
||||||
toast.error('Failed to consume credits, please try again later.');
|
toast.error('Failed to consume credits, please try again later.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,7 +44,9 @@ export function ConsumeCreditCard() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleConsume}
|
onClick={handleConsume}
|
||||||
disabled={isLoading || loading}
|
disabled={
|
||||||
|
loading || isLoadingBalance || consumeCreditsMutation.isPending
|
||||||
|
}
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
>
|
>
|
||||||
<CoinsIcon className="size-4" />
|
<CoinsIcon className="size-4" />
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ActiveThemeProvider } from '@/components/layout/active-theme-provider';
|
import { ActiveThemeProvider } from '@/components/layout/active-theme-provider';
|
||||||
import { CreditsProvider } from '@/components/layout/credits-provider';
|
|
||||||
import { PaymentProvider } from '@/components/layout/payment-provider';
|
import { PaymentProvider } from '@/components/layout/payment-provider';
|
||||||
import { QueryProvider } from '@/components/providers/query-provider';
|
import { QueryProvider } from '@/components/providers/query-provider';
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||||
@ -65,9 +64,7 @@ export function Providers({ children, locale }: ProvidersProps) {
|
|||||||
<ActiveThemeProvider>
|
<ActiveThemeProvider>
|
||||||
<RootProvider theme={theme} i18n={{ locale, locales, translations }}>
|
<RootProvider theme={theme} i18n={{ locale, locales, translations }}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<PaymentProvider>
|
<PaymentProvider>{children}</PaymentProvider>
|
||||||
<CreditsProvider>{children}</CreditsProvider>
|
|
||||||
</PaymentProvider>
|
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</RootProvider>
|
</RootProvider>
|
||||||
</ActiveThemeProvider>
|
</ActiveThemeProvider>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { websiteConfig } from '@/config/website';
|
import { websiteConfig } from '@/config/website';
|
||||||
import { useCredits } from '@/hooks/use-credits';
|
import { useCreditBalance } from '@/hooks/use-credits-query';
|
||||||
import { useLocaleRouter } from '@/i18n/navigation';
|
import { useLocaleRouter } from '@/i18n/navigation';
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
import { CoinsIcon, Loader2Icon } from 'lucide-react';
|
import { CoinsIcon, Loader2Icon } from 'lucide-react';
|
||||||
@ -15,8 +15,8 @@ export function CreditsBalanceButton() {
|
|||||||
|
|
||||||
const router = useLocaleRouter();
|
const router = useLocaleRouter();
|
||||||
|
|
||||||
// Use the new useCredits hook
|
// Use TanStack Query hook for credit balance
|
||||||
const { balance, isLoading } = useCredits();
|
const { data: balance = 0, isLoading } = useCreditBalance();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
router.push(Routes.SettingsCredits);
|
router.push(Routes.SettingsCredits);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { websiteConfig } from '@/config/website';
|
import { websiteConfig } from '@/config/website';
|
||||||
import { useCredits } from '@/hooks/use-credits';
|
import { useCreditBalance } from '@/hooks/use-credits-query';
|
||||||
import { useLocaleRouter } from '@/i18n/navigation';
|
import { useLocaleRouter } from '@/i18n/navigation';
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
import { CoinsIcon, Loader2Icon } from 'lucide-react';
|
import { CoinsIcon, Loader2Icon } from 'lucide-react';
|
||||||
@ -16,8 +16,8 @@ export function CreditsBalanceMenu() {
|
|||||||
const t = useTranslations('Marketing.avatar');
|
const t = useTranslations('Marketing.avatar');
|
||||||
const router = useLocaleRouter();
|
const router = useLocaleRouter();
|
||||||
|
|
||||||
// Use the new useCredits hook
|
// Use TanStack Query hook for credit balance
|
||||||
const { balance, isLoading } = useCredits();
|
const { data: balance = 0, isLoading } = useCreditBalance();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
router.push(Routes.SettingsCredits);
|
router.push(Routes.SettingsCredits);
|
||||||
|
@ -1,31 +1,19 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { websiteConfig } from '@/config/website';
|
import { websiteConfig } from '@/config/website';
|
||||||
import { authClient } from '@/lib/auth-client';
|
|
||||||
import { useCreditsStore } from '@/stores/credits-store';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Credits Provider Component
|
* Credits Provider Component
|
||||||
*
|
*
|
||||||
* This component initializes the credits store when the user is authenticated
|
* This component is now simplified since TanStack Query handles data fetching automatically.
|
||||||
* and handles cleanup when the user logs out.
|
* It's kept for potential future credits-related providers.
|
||||||
* Only renders when credits are enabled in the website configuration.
|
* Only renders when credits are enabled in the website configuration.
|
||||||
*/
|
*/
|
||||||
export function CreditsProvider({ children }: { children: React.ReactNode }) {
|
export function CreditsProvider({ children }: { children: React.ReactNode }) {
|
||||||
// Only initialize credits store if credits are enabled
|
// Only render when credits are enabled
|
||||||
if (!websiteConfig.credits.enableCredits) {
|
if (!websiteConfig.credits.enableCredits) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { fetchCredits } = useCreditsStore();
|
|
||||||
const { data: session } = authClient.useSession();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (session?.user) {
|
|
||||||
fetchCredits(session.user);
|
|
||||||
}
|
|
||||||
}, [session?.user, fetchCredits]);
|
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { getCreditTransactionsAction } from '@/actions/get-credit-transactions';
|
|
||||||
import type { CreditTransaction } from '@/components/settings/credits/credit-transactions-table';
|
|
||||||
import { CreditTransactionsTable } from '@/components/settings/credits/credit-transactions-table';
|
import { CreditTransactionsTable } from '@/components/settings/credits/credit-transactions-table';
|
||||||
|
import { useCreditTransactions } from '@/hooks/use-credits-query';
|
||||||
import type { SortingState } from '@tanstack/react-table';
|
import type { SortingState } from '@tanstack/react-table';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Credit transactions component
|
* Credit transactions component
|
||||||
@ -16,57 +14,25 @@ export function CreditTransactions() {
|
|||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [data, setData] = useState<CreditTransaction[]>([]);
|
|
||||||
const [total, setTotal] = useState(0);
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([
|
const [sorting, setSorting] = useState<SortingState>([
|
||||||
{ id: 'createdAt', desc: true },
|
{ id: 'createdAt', desc: true },
|
||||||
]);
|
]);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const { data, isLoading } = useCreditTransactions(
|
||||||
setLoading(true);
|
pageIndex,
|
||||||
try {
|
pageSize,
|
||||||
const result = await getCreditTransactionsAction({
|
search,
|
||||||
pageIndex,
|
sorting
|
||||||
pageSize,
|
);
|
||||||
search,
|
|
||||||
sorting,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result?.data?.success) {
|
|
||||||
setData(result.data.data?.items || []);
|
|
||||||
setTotal(result.data.data?.total || 0);
|
|
||||||
} else {
|
|
||||||
const errorMessage = result?.data?.error || t('error');
|
|
||||||
toast.error(errorMessage);
|
|
||||||
setData([]);
|
|
||||||
setTotal(0);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
'CreditTransactions, fetch credit transactions error:',
|
|
||||||
error
|
|
||||||
);
|
|
||||||
toast.error(t('error'));
|
|
||||||
setData([]);
|
|
||||||
setTotal(0);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, [pageIndex, pageSize, search, sorting]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, [fetchData]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CreditTransactionsTable
|
<CreditTransactionsTable
|
||||||
data={data}
|
data={data?.items || []}
|
||||||
total={total}
|
total={data?.total || 0}
|
||||||
pageIndex={pageIndex}
|
pageIndex={pageIndex}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
search={search}
|
search={search}
|
||||||
loading={loading}
|
loading={isLoading}
|
||||||
onSearch={setSearch}
|
onSearch={setSearch}
|
||||||
onPageChange={setPageIndex}
|
onPageChange={setPageIndex}
|
||||||
onPageSizeChange={setPageSize}
|
onPageSizeChange={setPageSize}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { getCreditStatsAction } from '@/actions/get-credit-stats';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -12,7 +11,7 @@ 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 { useCredits } from '@/hooks/use-credits';
|
import { useCreditBalance, useCreditStats } from '@/hooks/use-credits-query';
|
||||||
import { useMounted } from '@/hooks/use-mounted';
|
import { useMounted } from '@/hooks/use-mounted';
|
||||||
import { usePayment } from '@/hooks/use-payment';
|
import { usePayment } from '@/hooks/use-payment';
|
||||||
import { useLocaleRouter } from '@/i18n/navigation';
|
import { useLocaleRouter } from '@/i18n/navigation';
|
||||||
@ -22,7 +21,7 @@ import { Routes } from '@/routes';
|
|||||||
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';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,50 +39,24 @@ export default function CreditsBalanceCard() {
|
|||||||
const hasHandledSession = useRef(false);
|
const hasHandledSession = useRef(false);
|
||||||
const mounted = useMounted();
|
const mounted = useMounted();
|
||||||
|
|
||||||
// Use the credits hook to get balance
|
// Use TanStack Query hooks for credits
|
||||||
const {
|
const {
|
||||||
balance,
|
data: balance = 0,
|
||||||
isLoading: isLoadingBalance,
|
isLoading: isLoadingBalance,
|
||||||
error,
|
error,
|
||||||
fetchCredits,
|
refetch: refetchCredits,
|
||||||
} = useCredits();
|
} = useCreditBalance();
|
||||||
|
|
||||||
// Get payment info to check plan type
|
// Get payment info to check plan type
|
||||||
const { currentPlan } = usePayment();
|
const { currentPlan } = usePayment();
|
||||||
|
|
||||||
// State for credit statistics
|
// TanStack Query hook for credit statistics
|
||||||
const [creditStats, setCreditStats] = useState<{
|
const {
|
||||||
expiringCredits: {
|
data: creditStats,
|
||||||
amount: number;
|
isLoading: isLoadingStats,
|
||||||
earliestExpiration: string | Date | null;
|
error: statsError,
|
||||||
};
|
refetch: refetchCreditStats,
|
||||||
subscriptionCredits: { amount: number };
|
} = useCreditStats();
|
||||||
lifetimeCredits: { amount: number };
|
|
||||||
} | null>(null);
|
|
||||||
const [isLoadingStats, setIsLoadingStats] = useState(true);
|
|
||||||
|
|
||||||
// Fetch credit statistics
|
|
||||||
const fetchCreditStats = useCallback(async () => {
|
|
||||||
console.log('fetchCreditStats, fetch start');
|
|
||||||
setIsLoadingStats(true);
|
|
||||||
try {
|
|
||||||
const result = await getCreditStatsAction();
|
|
||||||
if (result?.data?.success && result.data.data) {
|
|
||||||
setCreditStats(result.data.data);
|
|
||||||
} else {
|
|
||||||
console.error('fetchCreditStats, failed to fetch credit stats', result);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('fetchCreditStats, error:', error);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingStats(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Fetch stats on component mount
|
|
||||||
useEffect(() => {
|
|
||||||
fetchCreditStats();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Check for payment success and show success message
|
// Check for payment success and show success message
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -96,9 +69,9 @@ export default function CreditsBalanceCard() {
|
|||||||
toast.success(t('creditsAdded'));
|
toast.success(t('creditsAdded'));
|
||||||
|
|
||||||
// Force refresh credits data to show updated balance
|
// Force refresh credits data to show updated balance
|
||||||
fetchCredits(true);
|
refetchCredits();
|
||||||
// Refresh credit stats
|
// Refresh credit stats
|
||||||
fetchCreditStats();
|
refetchCreditStats();
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Clean up URL parameters
|
// Clean up URL parameters
|
||||||
@ -106,16 +79,16 @@ export default function CreditsBalanceCard() {
|
|||||||
url.searchParams.delete('credits_session_id');
|
url.searchParams.delete('credits_session_id');
|
||||||
localeRouter.replace(Routes.SettingsCredits + url.search);
|
localeRouter.replace(Routes.SettingsCredits + url.search);
|
||||||
}
|
}
|
||||||
}, [searchParams, localeRouter, fetchCredits, fetchCreditStats, t]);
|
}, [searchParams, localeRouter, refetchCredits, refetchCreditStats, 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 refresh credits balance (ignore cache)
|
||||||
fetchCredits(true);
|
refetchCredits();
|
||||||
// Refresh credit stats
|
// Refresh credit stats
|
||||||
fetchCreditStats();
|
refetchCreditStats();
|
||||||
}, [fetchCredits, fetchCreditStats]);
|
}, [refetchCredits, refetchCreditStats]);
|
||||||
|
|
||||||
// Render loading skeleton
|
// Render loading skeleton
|
||||||
const isPageLoading = isLoadingBalance || isLoadingStats;
|
const isPageLoading = isLoadingBalance || isLoadingStats;
|
||||||
@ -140,7 +113,7 @@ export default function CreditsBalanceCard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render error state
|
// Render error state
|
||||||
if (error) {
|
if (error || 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>
|
||||||
@ -148,7 +121,9 @@ export default function CreditsBalanceCard() {
|
|||||||
<CardDescription>{t('description')}</CardDescription>
|
<CardDescription>{t('description')}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 flex-1">
|
<CardContent className="space-y-4 flex-1">
|
||||||
<div className="text-destructive text-sm">{error}</div>
|
<div className="text-destructive text-sm">
|
||||||
|
{error?.message || statsError?.message}
|
||||||
|
</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">
|
||||||
<Button
|
<Button
|
||||||
|
117
src/hooks/use-credits-query.ts
Normal file
117
src/hooks/use-credits-query.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { consumeCreditsAction } from '@/actions/consume-credits';
|
||||||
|
import { getCreditBalanceAction } from '@/actions/get-credit-balance';
|
||||||
|
import { getCreditStatsAction } from '@/actions/get-credit-stats';
|
||||||
|
import { getCreditTransactionsAction } from '@/actions/get-credit-transactions';
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import type { SortingState } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
// Query keys
|
||||||
|
export const creditsKeys = {
|
||||||
|
all: ['credits'] as const,
|
||||||
|
balance: () => [...creditsKeys.all, 'balance'] as const,
|
||||||
|
stats: () => [...creditsKeys.all, 'stats'] as const,
|
||||||
|
transactions: () => [...creditsKeys.all, 'transactions'] as const,
|
||||||
|
transactionsList: (filters: {
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
search: string;
|
||||||
|
sorting: SortingState;
|
||||||
|
}) => [...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
|
||||||
|
export function useCreditBalance() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: creditsKeys.balance(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const result = await getCreditBalanceAction();
|
||||||
|
if (!result?.data?.success) {
|
||||||
|
throw new Error('Failed to fetch credit balance');
|
||||||
|
}
|
||||||
|
return result.data.credits || 0;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook to consume credits
|
||||||
|
export function useConsumeCredits() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
amount,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
amount: number;
|
||||||
|
description: string;
|
||||||
|
}) => {
|
||||||
|
const result = await consumeCreditsAction({
|
||||||
|
amount,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
if (!result?.data?.success) {
|
||||||
|
throw new Error(result?.data?.error || 'Failed to consume credits');
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate credit balance and stats after consuming credits
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: creditsKeys.balance(),
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: creditsKeys.stats(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook to fetch credit transactions with pagination, search, and sorting
|
||||||
|
export function useCreditTransactions(
|
||||||
|
pageIndex: number,
|
||||||
|
pageSize: number,
|
||||||
|
search: string,
|
||||||
|
sorting: SortingState
|
||||||
|
) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: creditsKeys.transactionsList({
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
search,
|
||||||
|
sorting,
|
||||||
|
}),
|
||||||
|
queryFn: async () => {
|
||||||
|
const result = await getCreditTransactionsAction({
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
search,
|
||||||
|
sorting,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result?.data?.success) {
|
||||||
|
throw new Error(
|
||||||
|
result?.data?.error || 'Failed to fetch credit transactions'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: result.data.data?.items || [],
|
||||||
|
total: result.data.data?.total || 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@ -6,7 +6,7 @@ import type { SortingState } from '@tanstack/react-table';
|
|||||||
// Query keys
|
// Query keys
|
||||||
export const usersKeys = {
|
export const usersKeys = {
|
||||||
all: ['users'] as const,
|
all: ['users'] as const,
|
||||||
lists: () => [...usersKeys.all, 'list'] as const,
|
lists: () => [...usersKeys.all, 'lists'] as const,
|
||||||
list: (filters: {
|
list: (filters: {
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user