soapbox/src/features/wallet/hooks/useHooks.ts

297 wiersze
9.2 KiB
TypeScript

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { defineMessages, useIntl } from 'react-intl';
import { create } from 'zustand';
import { useApi } from 'soapbox/hooks/useApi.ts';
import { NutzappedEntry, NutzappedRecord, Transactions, WalletData, baseWalletSchema, nutzappedEntry, transactionsSchema } from 'soapbox/schemas/wallet.ts';
import toast from 'soapbox/toast.tsx';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities.ts';
const messages = defineMessages({
createWallet: { id: 'wallet.create.success', defaultMessage: 'Wallet created successfully!' },
success: { id: 'wallet.success', defaultMessage: 'Data updated with success!' },
error: { id: 'wallet.error', defaultMessage: 'Failed to update.' },
});
interface WalletState {
wallet: WalletData | null;
transactions: Transactions | null;
zapCashuList: string[];
nutzappedRecord: NutzappedRecord;
prevTransaction?: string | null;
nextTransaction?: string | null;
prevZaps?: string | null;
nextZaps?: string | null;
hasFetchedWallet: boolean;
hasFetchedTransactions: boolean;
setNutzappedRecord: (statusId: string, nutzappedEntry: NutzappedEntry, prevZaps?: string | null, nextZaps?: string | null) => void;
setWallet: (wallet: WalletData | null) => void;
setHasFetchedWallet: (hasFetchedWallet: boolean) => void;
setTransactions: (transactions: Transactions | null, prevTransaction?: string | null, nextTransaction?: string | null) => void;
setHasFetchedTransactions: (hasFetched: boolean) => void;
addZapCashu: (statusId: string) => void;
}
interface IWalletInfo {
mints: string[];
relays: string[];
}
interface IZapCashuPayload {
account: AccountEntity;
amount: number;
comment: string;
status?: StatusEntity;
}
const useWalletStore = create<WalletState>((set) => ({
wallet: null,
acceptsZapsCashu: false,
transactions: null,
prevTransaction: null,
nextTransaction: null,
prevZaps: null,
nextZaps: null,
zapCashuList: [],
nutzappedRecord: {},
hasFetchedWallet: false,
hasFetchedTransactions: false,
setNutzappedRecord: (statusId, nutzappedEntry, prevZaps, nextZaps) => set((state)=> ({
nutzappedRecord: {
...state.nutzappedRecord,
[statusId]: nutzappedEntry,
},
prevZaps,
nextZaps,
})),
setWallet: (wallet) => set({ wallet }),
setHasFetchedWallet: (hasFetchedWallet) => set({ hasFetchedWallet }),
setTransactions: (transactions, prevTransaction, nextTransaction) => set({ transactions, prevTransaction, nextTransaction }),
setHasFetchedTransactions: (hasFetched) => set({ hasFetchedTransactions: hasFetched }),
addZapCashu: (statusId) =>
set((state) => ({
zapCashuList: [
...state.zapCashuList,
statusId,
],
})),
}));
const useCreateWallet = () => {
const api = useApi();
const intl = useIntl();
const queryClient = useQueryClient();
const { setWallet } = useWalletStore();
const createWallet = useMutation({
mutationFn: async (walletInfo: IWalletInfo) => {
const response = await api.put('/api/v1/ditto/cashu/wallet', walletInfo);
const data = await response.json();
return baseWalletSchema.parse(data);
},
onSuccess: (data) => {
toast.success(intl.formatMessage(messages.createWallet));
setWallet(data);
queryClient.invalidateQueries({ queryKey: ['wallet'] });
},
onError: (error: any) => {
toast.error(error?.message || 'An error occurred while creating the wallet');
},
});
return { createWallet: createWallet.mutateAsync };
};
const useWallet = () => {
const api = useApi();
const {
setWallet,
setHasFetchedWallet,
} = useWalletStore();
const getWallet = useQuery({
queryKey: ['wallet'],
queryFn: async () => {
const response = await api.get('/api/v1/ditto/cashu/wallet');
const data = await response.json();
const normalizedData = baseWalletSchema.parse(data);
setWallet(normalizedData);
setHasFetchedWallet(true);
return normalizedData;
},
staleTime: 1000 * 60 * 2,
retry: false,
});
return {
walletData: getWallet.data,
error: getWallet.error?.message,
isLoading: getWallet.isLoading,
getWallet: getWallet.refetch,
};
};
const useTransactions = () => {
const api = useApi();
const { setTransactions } = useWalletStore();
const getTransactions = useQuery({
queryKey: ['transactions'],
queryFn: async () => {
const response = await api.get('/api/v1/ditto/cashu/transactions');
const { prev, next: nextPag } = response.pagination();
const data = await response.json();
const normalizedData = transactionsSchema.parse(data);
setTransactions(normalizedData, prev, nextPag);
return normalizedData;
},
});
const expandTransactions = useMutation({
mutationFn: async () => {
const { transactions, nextTransaction } = useWalletStore.getState();
if (!nextTransaction || !transactions) {
return;
}
const response = await api.get(nextTransaction);
const { prev, next: nextPag } = response.pagination();
const data = await response.json();
const normalizedData = transactionsSchema.parse(data);
const newTransactions = [...transactions, ...normalizedData];
setTransactions(newTransactions, prev, nextPag);
},
onError: (error: any) => {
toast.error(error?.message || 'Error expanding transactions');
},
});
return {
transactions: getTransactions.data,
isLoading: getTransactions.isLoading,
error: getTransactions.error,
refetch: getTransactions.refetch,
expandTransactions: expandTransactions.mutateAsync,
isExpanding: expandTransactions.isPending,
};
};
const useZapCashuRequest = () => {
const api = useApi();
const { addZapCashu } = useWalletStore();
const { refetch } = useTransactions();
const queryClient = useQueryClient();
const zapCashuRequest = useMutation({
mutationFn: async ({ account, amount, comment, status }: IZapCashuPayload) => {
await api.post('/api/v1/ditto/cashu/nutzap', {
amount,
comment,
account_id: account.id,
status_id: status?.id,
});
if (status) {
addZapCashu(status.id);
}
},
onSuccess: async () =>{
toast.success('Sats sent successfully!');
queryClient.invalidateQueries({ queryKey: ['wallet'] });
await refetch();
},
onError: (err: unknown) => {
const messageError = err instanceof Error ? err.message : 'An unexpected error occurred';
toast.error(messageError);
},
});
return { zapCashu: zapCashuRequest.mutateAsync, isLoading: zapCashuRequest.isPending };
};
const useZappedByCashu = (statusId: string) => {
const api = useApi();
const { nextZaps, nutzappedRecord, setNutzappedRecord } = useWalletStore();
const getNutzappedBy = useQuery({
queryKey: ['nutzappedBy', statusId],
queryFn: async () => {
const response = await api.get(`/api/v1/ditto/cashu/statuses/${statusId}/nutzapped_by`);
const { prev, next } = response.pagination();
const data = await response.json();
const normalizedData = nutzappedEntry.parse(data);
setNutzappedRecord(statusId, normalizedData, prev, next);
return normalizedData;
},
enabled: !!statusId,
retry: false,
staleTime: 1000 * 60 * 2,
});
const expandNutzappedBy = useMutation({
mutationFn: async () => {
if (!nextZaps || !nutzappedRecord[statusId]) {
throw new Error('No more zaps to load');
}
const response = await api.get(nextZaps);
const { prev, next } = response.pagination();
const data = await response.json();
const normalizedData = nutzappedEntry.parse(data);
const newNutzappedBy = [...(nutzappedRecord[statusId] ?? []), ...normalizedData];
setNutzappedRecord(statusId, newNutzappedBy, prev, next);
return newNutzappedBy;
},
onError: (err: unknown) => {
const messageError = err instanceof Error ? err.message : 'Error expanding zaps';
toast.error(messageError);
},
});
return {
error: getNutzappedBy.error,
isLoading: getNutzappedBy.isLoading || expandNutzappedBy.isPending,
getNutzappedBy: getNutzappedBy.refetch,
expandNutzappedBy: expandNutzappedBy.mutateAsync,
};
};
const useUpdateWallet = () => {
const api = useApi();
const queryClient = useQueryClient();
const intl = useIntl();
const updateWallet = useMutation({
mutationFn: async ({ mints, relays }: IWalletInfo) => {
await api.put('/api/v1/ditto/cashu/wallet', { mints: mints, relays: relays });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['wallet'] });
toast.success(intl.formatMessage(messages.success));
},
onError: (err: Error) => {
const errorMessage = err instanceof Error ? err.message : intl.formatMessage(messages.error);
toast.error(errorMessage);
console.error(err);
},
});
return {
updateWallet: updateWallet.mutateAsync,
isSuccess: updateWallet.isSuccess,
error: updateWallet.error,
isLoading: updateWallet.isPending,
};
};
export { useWalletStore, useWallet, useCreateWallet, useTransactions, useZapCashuRequest, useZappedByCashu, useUpdateWallet };