From 13c23dab561f8b4de43116860e72065690f03b9d Mon Sep 17 00:00:00 2001 From: javayhu Date: Thu, 21 Aug 2025 00:54:40 +0800 Subject: [PATCH] refactor: migrate state management from Zustand to TanStack Query for improved data fetching and caching across components --- TANSTACK_QUERY_REFACTOR.md | 177 ++++++++++++++++++ .../text/components/consume-credit-card.tsx | 2 +- src/components/dashboard/upgrade-card.tsx | 2 +- .../layout/credits-balance-button.tsx | 2 +- .../layout/credits-balance-menu.tsx | 2 +- .../settings/billing/billing-card.tsx | 2 +- .../settings/credits/credit-packages.tsx | 2 +- .../settings/credits/credit-transactions.tsx | 2 +- .../settings/credits/credits-balance-card.tsx | 4 +- src/hooks/use-credits-query.ts | 117 ------------ src/hooks/use-credits.ts | 173 +++++++++++------ .../{use-payment-query.ts => use-payment.ts} | 0 src/stores/credits-store.ts | 163 ---------------- src/stores/users-store.ts | 12 -- 14 files changed, 298 insertions(+), 362 deletions(-) create mode 100644 TANSTACK_QUERY_REFACTOR.md delete mode 100644 src/hooks/use-credits-query.ts rename src/hooks/{use-payment-query.ts => use-payment.ts} (100%) delete mode 100644 src/stores/credits-store.ts delete mode 100644 src/stores/users-store.ts diff --git a/TANSTACK_QUERY_REFACTOR.md b/TANSTACK_QUERY_REFACTOR.md new file mode 100644 index 0000000..f03b90f --- /dev/null +++ b/TANSTACK_QUERY_REFACTOR.md @@ -0,0 +1,177 @@ +# TanStack Query 重构总结 + +## 概述 + +本次重构将项目中的状态管理从 Zustand stores 迁移到 TanStack Query,提供了更好的数据获取、缓存和状态管理能力。 + +## 重构的组件 + +### 1. NewsletterFormCard +- **文件**: `src/components/settings/notification/newsletter-form-card.tsx` +- **重构内容**: + - 使用 `useNewsletterStatus`, `useSubscribeNewsletter`, `useUnsubscribeNewsletter` hooks + - 移除了手动的 `useState` 状态管理 + - 使用 TanStack Query 的自动缓存和错误处理 + +### 2. UsersPageClient +- **文件**: `src/components/admin/users-page.tsx` +- **重构内容**: + - 使用 `useUsers` hook 进行数据获取 + - 移除了手动的数据获取逻辑和状态管理 + - 简化了组件逻辑 + +### 3. CreditsBalanceCard +- **文件**: `src/components/settings/credits/credits-balance-card.tsx` +- **重构内容**: + - 使用 `useCreditStats` hook 获取信用统计信息 + - 移除了手动的 `fetchCreditStats` 函数 + - 使用 TanStack Query 的 `refetch` 功能 + +### 4. CreditTransactions +- **文件**: `src/components/settings/credits/credit-transactions.tsx` +- **重构内容**: + - 使用 `useCreditTransactions` hook 进行分页数据获取 + - 移除了手动的数据获取和状态管理 + - 简化了组件逻辑 + +### 5. BillingCard +- **文件**: `src/components/settings/billing/billing-card.tsx` +- **重构内容**: + - 使用 `useCurrentPlan` hook 获取支付和订阅信息 + - 移除了对 `usePayment` hook 的依赖 + - 使用 TanStack Query 的自动数据获取 + +### 6. UserDetailViewer +- **文件**: `src/components/admin/user-detail-viewer.tsx` +- **重构内容**: + - 使用 `useBanUser`, `useUnbanUser` mutation hooks + - 移除了对 `useUsersStore` 的依赖 + - 使用 TanStack Query 的自动缓存失效 + +## 新增的 Hooks + +### 1. Newsletter Hooks (`src/hooks/use-newsletter.ts`) +- `useNewsletterStatus(email)` - 查询订阅状态 +- `useSubscribeNewsletter()` - 订阅 newsletter +- `useUnsubscribeNewsletter()` - 取消订阅 newsletter + +### 2. Users Hooks (`src/hooks/use-users.ts`) +- `useUsers(pageIndex, pageSize, search, sorting)` - 获取用户列表 +- `useBanUser()` - 封禁用户 +- `useUnbanUser()` - 解封用户 + +### 3. Credits Hooks (`src/hooks/use-credits-query.ts`) +- `useCreditBalance()` - 获取信用余额 +- `useConsumeCredits()` - 消费信用 +- `useCreditStats()` - 获取信用统计信息 +- `useCreditTransactions(pageIndex, pageSize, search, sorting)` - 获取信用交易记录 + +### 4. Payment Hooks (`src/hooks/use-payment-query.ts`) +- `useActiveSubscription(userId)` - 获取活跃订阅 +- `useLifetimeStatus(userId)` - 获取终身会员状态 +- `useCurrentPlan(userId)` - 获取当前计划信息 + +## 简化的 Providers + +### 1. PaymentProvider +- **文件**: `src/components/layout/payment-provider.tsx` +- **状态**: 已移除,TanStack Query 自动处理所有支付相关数据获取 + +### 2. CreditsProvider +- **文件**: `src/components/layout/credits-provider.tsx` +- **状态**: 已移除,TanStack Query 自动处理所有积分相关数据获取 + +## 更新的组件 + +### 1. UserButton +- **文件**: `src/components/layout/user-button.tsx` +- **更新**: 移除了 `resetState` 调用,TanStack Query 自动处理缓存失效 + +### 2. UserButtonMobile +- **文件**: `src/components/layout/user-button-mobile.tsx` +- **更新**: 移除了 `resetState` 调用,TanStack Query 自动处理缓存失效 + +### 3. SidebarUser +- **文件**: `src/components/dashboard/sidebar-user.tsx` +- **更新**: 移除了 `resetState` 调用,TanStack Query 自动处理缓存失效 + +### 4. CreditsBalanceButton +- **文件**: `src/components/layout/credits-balance-button.tsx` +- **更新**: 使用 `useCreditBalance` 替代 `useCredits` + +### 5. CreditsBalanceMenu +- **文件**: `src/components/layout/credits-balance-menu.tsx` +- **更新**: 使用 `useCreditBalance` 替代 `useCredits` + +### 6. ConsumeCreditCard +- **文件**: `src/ai/text/components/consume-credit-card.tsx` +- **更新**: 使用 `useCreditBalance` 和 `useConsumeCredits` 替代 `useCredits` + +### 7. UpgradeCard +- **文件**: `src/components/dashboard/upgrade-card.tsx` +- **更新**: 使用 `useCurrentPlan` 替代 `usePayment` + +### 8. CreditPackages +- **文件**: `src/components/settings/credits/credit-packages.tsx` +- **更新**: 使用 `useCurrentPlan` 替代 `usePayment` + +### 9. CreditsBalanceCard +- **文件**: `src/components/settings/credits/credits-balance-card.tsx` +- **更新**: 使用 `useCurrentPlan` 替代 `usePayment` + +## 配置 + +### 1. QueryClient 配置 +- **文件**: `src/lib/query-client.ts` +- **配置**: 设置了合理的缓存时间和重试策略 + +### 2. QueryProvider +- **文件**: `src/components/providers/query-provider.tsx` +- **功能**: 提供 TanStack Query 上下文和开发工具 + +## 优势 + +### 1. 更好的缓存管理 +- 自动缓存数据,减少不必要的网络请求 +- 智能的缓存失效策略 +- 支持乐观更新 + +### 2. 简化的状态管理 +- 移除了大量的 `useState` 和 `useEffect` +- 自动处理加载和错误状态 +- 统一的数据获取模式 + +### 3. 更好的用户体验 +- 自动重试失败的请求 +- 后台数据刷新 +- 更流畅的加载状态 + +### 4. 开发体验提升 +- 内置的开发工具支持 +- 更好的错误处理 +- 类型安全的数据获取 + +## 缓存策略 + +- **用户数据**: 30秒缓存,5分钟垃圾回收 +- **信用数据**: 30秒缓存,5分钟垃圾回收 +- **信用统计**: 1分钟缓存,10分钟垃圾回收 +- **支付数据**: 2分钟缓存,5分钟垃圾回收 +- **终身状态**: 5分钟缓存,10分钟垃圾回收 + +## 注意事项 + +1. 所有组件现在都使用 TanStack Query 进行数据获取 +2. 移除了对 Zustand stores 的依赖(除了必要的全局状态) +3. 错误处理现在通过 TanStack Query 统一管理 +4. 加载状态通过 `isLoading` 和 `isPending` 属性获取 +5. 缓存失效通过 `invalidateQueries` 自动处理 +6. 完全移除了 PaymentProvider 和 CreditsProvider +7. 删除了 use-payment.ts 和 payment-store.ts 文件 + +## 测试 + +- ✅ 所有组件编译通过 +- ✅ TypeScript 类型检查通过 +- ✅ 构建成功 +- ✅ 代码格式化通过 diff --git a/src/ai/text/components/consume-credit-card.tsx b/src/ai/text/components/consume-credit-card.tsx index 5ca5c27..ea4f828 100644 --- a/src/ai/text/components/consume-credit-card.tsx +++ b/src/ai/text/components/consume-credit-card.tsx @@ -2,7 +2,7 @@ import { CreditsBalanceButton } from '@/components/layout/credits-balance-button'; import { Button } from '@/components/ui/button'; -import { useConsumeCredits, useCreditBalance } from '@/hooks/use-credits-query'; +import { useConsumeCredits, useCreditBalance } from '@/hooks/use-credits'; import { CoinsIcon } from 'lucide-react'; import { useState } from 'react'; import { toast } from 'sonner'; diff --git a/src/components/dashboard/upgrade-card.tsx b/src/components/dashboard/upgrade-card.tsx index 0d79982..6807aa0 100644 --- a/src/components/dashboard/upgrade-card.tsx +++ b/src/components/dashboard/upgrade-card.tsx @@ -9,7 +9,7 @@ import { CardTitle, } from '@/components/ui/card'; import { websiteConfig } from '@/config/website'; -import { useCurrentPlan } from '@/hooks/use-payment-query'; +import { useCurrentPlan } from '@/hooks/use-payment'; import { LocaleLink } from '@/i18n/navigation'; import { authClient } from '@/lib/auth-client'; import { Routes } from '@/routes'; diff --git a/src/components/layout/credits-balance-button.tsx b/src/components/layout/credits-balance-button.tsx index 16c3fda..fa8baf8 100644 --- a/src/components/layout/credits-balance-button.tsx +++ b/src/components/layout/credits-balance-button.tsx @@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button'; import { websiteConfig } from '@/config/website'; -import { useCreditBalance } from '@/hooks/use-credits-query'; +import { useCreditBalance } from '@/hooks/use-credits'; import { useLocaleRouter } from '@/i18n/navigation'; import { Routes } from '@/routes'; import { CoinsIcon, Loader2Icon } from 'lucide-react'; diff --git a/src/components/layout/credits-balance-menu.tsx b/src/components/layout/credits-balance-menu.tsx index 1e331e4..512dd11 100644 --- a/src/components/layout/credits-balance-menu.tsx +++ b/src/components/layout/credits-balance-menu.tsx @@ -1,7 +1,7 @@ 'use client'; import { websiteConfig } from '@/config/website'; -import { useCreditBalance } from '@/hooks/use-credits-query'; +import { useCreditBalance } from '@/hooks/use-credits'; import { useLocaleRouter } from '@/i18n/navigation'; import { Routes } from '@/routes'; import { CoinsIcon, Loader2Icon } from 'lucide-react'; diff --git a/src/components/settings/billing/billing-card.tsx b/src/components/settings/billing/billing-card.tsx index 28e619d..494dbbc 100644 --- a/src/components/settings/billing/billing-card.tsx +++ b/src/components/settings/billing/billing-card.tsx @@ -14,7 +14,7 @@ import { import { Skeleton } from '@/components/ui/skeleton'; import { getPricePlans } from '@/config/price-config'; import { useMounted } from '@/hooks/use-mounted'; -import { useCurrentPlan } from '@/hooks/use-payment-query'; +import { useCurrentPlan } from '@/hooks/use-payment'; import { LocaleLink, useLocaleRouter } from '@/i18n/navigation'; import { authClient } from '@/lib/auth-client'; import { formatDate } from '@/lib/formatter'; diff --git a/src/components/settings/credits/credit-packages.tsx b/src/components/settings/credits/credit-packages.tsx index a2ac0fe..64a1abd 100644 --- a/src/components/settings/credits/credit-packages.tsx +++ b/src/components/settings/credits/credit-packages.tsx @@ -11,7 +11,7 @@ import { import { getCreditPackages } from '@/config/credits-config'; import { websiteConfig } from '@/config/website'; import { useCurrentUser } from '@/hooks/use-current-user'; -import { useCurrentPlan } from '@/hooks/use-payment-query'; +import { useCurrentPlan } from '@/hooks/use-payment'; import { authClient } from '@/lib/auth-client'; import { formatPrice } from '@/lib/formatter'; import { cn } from '@/lib/utils'; diff --git a/src/components/settings/credits/credit-transactions.tsx b/src/components/settings/credits/credit-transactions.tsx index 9eea2ad..608f0c9 100644 --- a/src/components/settings/credits/credit-transactions.tsx +++ b/src/components/settings/credits/credit-transactions.tsx @@ -1,7 +1,7 @@ 'use client'; import { CreditTransactionsTable } from '@/components/settings/credits/credit-transactions-table'; -import { useCreditTransactions } from '@/hooks/use-credits-query'; +import { useCreditTransactions } from '@/hooks/use-credits'; import type { SortingState } from '@tanstack/react-table'; import { useTranslations } from 'next-intl'; import { useState } from 'react'; diff --git a/src/components/settings/credits/credits-balance-card.tsx b/src/components/settings/credits/credits-balance-card.tsx index 398f0f9..54c2f21 100644 --- a/src/components/settings/credits/credits-balance-card.tsx +++ b/src/components/settings/credits/credits-balance-card.tsx @@ -11,9 +11,9 @@ import { } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import { websiteConfig } from '@/config/website'; -import { useCreditBalance, useCreditStats } from '@/hooks/use-credits-query'; +import { useCreditBalance, useCreditStats } from '@/hooks/use-credits'; import { useMounted } from '@/hooks/use-mounted'; -import { useCurrentPlan } from '@/hooks/use-payment-query'; +import { useCurrentPlan } from '@/hooks/use-payment'; import { useLocaleRouter } from '@/i18n/navigation'; import { authClient } from '@/lib/auth-client'; import { formatDate } from '@/lib/formatter'; diff --git a/src/hooks/use-credits-query.ts b/src/hooks/use-credits-query.ts deleted file mode 100644 index 8794905..0000000 --- a/src/hooks/use-credits-query.ts +++ /dev/null @@ -1,117 +0,0 @@ -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, - }; - }, - }); -} diff --git a/src/hooks/use-credits.ts b/src/hooks/use-credits.ts index 2cfd0ee..8794905 100644 --- a/src/hooks/use-credits.ts +++ b/src/hooks/use-credits.ts @@ -1,66 +1,117 @@ -import { websiteConfig } from '@/config/website'; -import { authClient } from '@/lib/auth-client'; -import { useCreditsStore } from '@/stores/credits-store'; -import { useCallback, useEffect } from 'react'; +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'; -/** - * Hook for accessing and managing credits state - * - * 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, - }; - } +// 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, +}; - const { - balance, - isLoading, - error, - fetchCredits: fetchCreditsFromStore, - consumeCredits, - } = useCreditsStore(); - - const { data: session } = authClient.useSession(); - - const fetchCredits = useCallback( - (force = false) => { - const currentUser = session?.user; - if (currentUser) { - fetchCreditsFromStore(currentUser, force); +// 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; }, - [session?.user, fetchCreditsFromStore] - ); - - useEffect(() => { - const currentUser = session?.user; - if (currentUser) { - fetchCreditsFromStore(currentUser); - } - }, [session?.user, fetchCreditsFromStore]); - - return { - // State - balance, - isLoading, - error, - - // Methods - fetchCredits, - consumeCredits, - - // Helper methods - hasEnoughCredits: (amount: number) => balance >= amount, - }; + }); +} + +// 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, + }; + }, + }); } diff --git a/src/hooks/use-payment-query.ts b/src/hooks/use-payment.ts similarity index 100% rename from src/hooks/use-payment-query.ts rename to src/hooks/use-payment.ts diff --git a/src/stores/credits-store.ts b/src/stores/credits-store.ts deleted file mode 100644 index 0f7325e..0000000 --- a/src/stores/credits-store.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { consumeCreditsAction } from '@/actions/consume-credits'; -import { getCreditBalanceAction } from '@/actions/get-credit-balance'; -import type { Session } from '@/lib/auth-types'; -import { create } from 'zustand'; - -// Cache duration: 2 minutes (optimized for better UX) -const CACHE_DURATION = 2 * 60 * 1000; - -/** - * Credits state interface - */ -export interface CreditsState { - // Current credit balance - balance: number; - // Loading state - isLoading: boolean; - // Error state - error: string | null; - // Last fetch timestamp to avoid frequent requests - lastFetchTime: number | null; - - // Actions - fetchCredits: ( - user: Session['user'] | null | undefined, - force?: boolean - ) => Promise; - consumeCredits: (amount: number, description: string) => Promise; -} - -/** - * Credits store using Zustand - * Manages the user's credit balance globally with caching and optimistic updates - */ -export const useCreditsStore = create((set, get) => ({ - // Initial state - balance: 0, - isLoading: false, - error: null, - lastFetchTime: null, - - /** - * Fetch credit balance for the current user with optional cache bypass - * @param user Current user from auth session - * @param force Whether to force refresh and ignore cache - */ - fetchCredits: async (user, force = false) => { - // Skip if already loading - if (get().isLoading) return; - - // Skip if no user is provided - if (!user) { - set({ - balance: 0, - error: null, - lastFetchTime: null, - }); - return; - } - - // Check if we have recent data (within cache duration) unless force refresh - if (!force) { - const { lastFetchTime } = get(); - const now = Date.now(); - if (lastFetchTime && now - lastFetchTime < CACHE_DURATION) { - return; // Use cached data - } - } - - console.log(`fetchCredits, ${force ? 'force fetch' : 'fetch'} credits`); - set({ - isLoading: true, - error: null, - // Clear cache if force refresh - lastFetchTime: force ? null : get().lastFetchTime, - }); - - try { - const result = await getCreditBalanceAction(); - if (result?.data?.success && result.data.credits !== undefined) { - const newBalance = result.data.credits || 0; - console.log('fetchCredits, set new balance', newBalance); - set({ - balance: newBalance, - isLoading: false, - error: null, - lastFetchTime: Date.now(), - }); - } else { - console.warn('fetchCredits, failed to fetch credit balance', result); - set({ - error: - (result?.data as any)?.error || 'Failed to fetch credit balance', - isLoading: false, - }); - } - } catch (error) { - console.error('fetchCredits, error:', error); - set({ - error: 'Failed to fetch credit balance', - isLoading: false, - }); - } - }, - - /** - * Consume credits with optimistic updates - * @param amount Amount of credits to consume - * @param description Description for the transaction - * @returns Promise Success status - */ - consumeCredits: async (amount: number, description: string) => { - const { balance } = get(); - - // Check if we have enough credits - if (balance < amount) { - console.log('consumeCredits, insufficient credits', balance, amount); - set({ - error: 'Insufficient credits', - }); - return false; - } - - // Optimistically update the balance - set({ - balance: balance - amount, - error: null, - isLoading: true, - }); - - try { - const result = await consumeCreditsAction({ - amount, - description, - }); - - if (result?.data?.success) { - set({ - isLoading: false, - error: null, - }); - return true; - } - - // Revert optimistic update on failure - console.warn('consumeCredits, reverting optimistic update'); - set({ - balance: balance, // Revert to original balance - error: result?.data?.error || 'Failed to consume credits', - isLoading: false, - }); - return false; - } catch (error) { - console.error('consumeCredits, error:', error); - // Revert optimistic update on error - set({ - balance: balance, // Revert to original balance - error: 'Failed to consume credits', - isLoading: false, - }); - return false; - } - }, -})); diff --git a/src/stores/users-store.ts b/src/stores/users-store.ts deleted file mode 100644 index e6b901e..0000000 --- a/src/stores/users-store.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { create } from 'zustand'; - -interface UsersState { - refreshTrigger: number; - triggerRefresh: () => void; -} - -export const useUsersStore = create((set) => ({ - refreshTrigger: 0, - triggerRefresh: () => - set((state) => ({ refreshTrigger: state.refreshTrigger + 1 })), -}));