/** * User Wallet Store - Platform-aware wallet management * Uses Firebase abstraction layer for cross-platform support */ import { create } from 'zustand'; import { doc } from '../firebase'; import type { UserWallet } from '../services/walletService'; export interface WalletCacheEntry { wallet: UserWallet | null; loading: boolean; error: string | null; lastFetched?: number; } interface UserWalletStoreState { wallets: Record; subscriptions: Record void>; ensureSubscription: (uid: string) => void; refreshWallet: (uid: string) => Promise; invalidateWallet: (uid: string) => Promise; removeWallet: (uid: string) => void; clearAll: () => void; setWalletState: (uid: string, updates: Partial) => void; } const buildEntry = ( existing: WalletCacheEntry | undefined, overrides: Partial ): WalletCacheEntry => ({ wallet: existing?.wallet ?? null, loading: existing?.loading ?? false, error: existing?.error ?? null, lastFetched: existing?.lastFetched, ...overrides, }); const snapshotExists = (docSnap: any): boolean => { const existsValue = docSnap?.exists; if (typeof existsValue === 'function') { try { return !!existsValue.call(docSnap); } catch { return false; } } return !!existsValue; }; export const useUserWalletStore = create((set, get) => ({ wallets: {}, subscriptions: {}, ensureSubscription: (uid: string) => { if (!uid) { return; } // If already subscribed, don't create another subscription if (get().subscriptions[uid]) { return; } const existing = get().wallets[uid]; if (existing?.loading) { return; } // If we have cached data, set up a real-time listener if (existing?.lastFetched) { const walletDocRef = doc('wallets', uid); const unsubscribe = walletDocRef.onSnapshot( (docSnap: any) => { const wallet = snapshotExists(docSnap) ? (docSnap.data() as UserWallet) : null; set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { wallet, loading: false, error: wallet ? null : 'Wallet not found', lastFetched: Date.now(), }), }, })); }, (error: any) => { set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { loading: false, error: error.message || 'Failed to fetch wallet', }), }, })); } ); set((state) => ({ subscriptions: { ...state.subscriptions, [uid]: unsubscribe, }, })); return; } // Initial fetch void get().refreshWallet(uid).catch((error) => { set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { loading: false, error: error instanceof Error ? error.message : 'Failed to fetch wallet', }), }, })); }); }, refreshWallet: async (uid: string) => { if (!uid) { return null; } set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { loading: true, error: null, }), }, })); try { const walletDocRef = doc('wallets', uid); const docSnap = await walletDocRef.get(); const wallet = snapshotExists(docSnap) ? (docSnap.data() as UserWallet) : null; set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { wallet, loading: false, error: wallet ? null : 'Wallet not found', lastFetched: Date.now(), }), }, })); // Set up real-time listener after initial fetch if not already subscribed if (!get().subscriptions[uid] && wallet) { const unsubscribe = walletDocRef.onSnapshot( (docSnap: any) => { const updatedWallet = snapshotExists(docSnap) ? (docSnap.data() as UserWallet) : null; set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { wallet: updatedWallet, loading: false, error: updatedWallet ? null : 'Wallet not found', lastFetched: Date.now(), }), }, })); }, (error: any) => { set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { loading: false, error: error.message || 'Failed to fetch wallet', }), }, })); } ); set((state) => ({ subscriptions: { ...state.subscriptions, [uid]: unsubscribe, }, })); } return wallet; } catch (error) { set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { loading: false, error: error instanceof Error ? error.message : 'Failed to fetch wallet', }), }, })); throw error; } }, invalidateWallet: async (uid: string) => { if (!uid) { return null; } set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], { loading: true, error: null, lastFetched: undefined, }), }, })); return get().refreshWallet(uid); }, removeWallet: (uid: string) => { // Unsubscribe from listener if exists const unsubscribe = get().subscriptions[uid]; if (unsubscribe) { unsubscribe(); } set((state) => { const { [uid]: _removedWallet, ...restWallets } = state.wallets; const { [uid]: _removedSub, ...restSubs } = state.subscriptions; return { wallets: restWallets, subscriptions: restSubs, }; }); }, clearAll: () => { // Unsubscribe from all listeners const subscriptions = get().subscriptions; Object.values(subscriptions).forEach((unsubscribe) => { unsubscribe(); }); set({ wallets: {}, subscriptions: {} }); }, setWalletState: (uid: string, updates: Partial) => { if (!uid) { return; } set((state) => ({ wallets: { ...state.wallets, [uid]: buildEntry(state.wallets[uid], updates), }, })); }, })); export const getWalletFromCache = (uid: string): UserWallet | null => { if (!uid) { return null; } return useUserWalletStore.getState().wallets[uid]?.wallet ?? null; };