Implement "nutzapped_by"

merge-requests/3333/head
danidfra 2025-03-27 14:26:27 -03:00
rodzic f3873b55ba
commit 1eec214a51
3 zmienionych plików z 76 dodań i 10 usunięć

Wyświetl plik

@ -8,6 +8,7 @@ import Modal from 'soapbox/components/ui/modal.tsx';
import Spinner from 'soapbox/components/ui/spinner.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import AccountContainer from 'soapbox/containers/account-container.tsx';
import { useWalletStore, useZappedByCashu } from 'soapbox/features/zap/hooks/useHooks.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { shortNumberFormat } from 'soapbox/utils/numbers.tsx';
@ -16,6 +17,7 @@ interface IAccountWithZaps {
id: string;
comment: string;
amount: number;
type: string;
}
interface IZapsModal {
@ -25,16 +27,22 @@ interface IZapsModal {
const ZapsModal: React.FC<IZapsModal> = ({ onClose, statusId }) => {
const dispatch = useAppDispatch();
const zaps = useAppSelector((state) => state.user_lists.zapped_by.get(statusId)?.items);
const next = useAppSelector((state) => state.user_lists.zapped_by.get(statusId)?.next);
const nutzaps = useWalletStore().nutzappedRecord[statusId];
const { getNutzappedBy } = useZappedByCashu();
const accounts = useMemo((): ImmutableList<IAccountWithZaps> | undefined => {
if (!zaps) return;
if (!zaps && !nutzaps) return;
return zaps
.map(({ account, amount, comment }) => ({ id: account, amount, comment }))
.flatten() as ImmutableList<IAccountWithZaps>;
}, [zaps]);
const zappedAccounts = zaps?.map(({ account, amount, comment }) => ({ id: account, amount, comment, type: 'zap' })) || [];
const nutzappedAccounts = nutzaps?.map(({ account, amount, comment }) => ({ id: account.id, amount, comment, type: 'nutzap' })) || [];
const combinedAccounts = [...zappedAccounts, ...nutzappedAccounts];
return ImmutableList(combinedAccounts);
}, [zaps, nutzaps]);
const fetchData = () => {
dispatch(fetchZaps(statusId));
@ -42,6 +50,7 @@ const ZapsModal: React.FC<IZapsModal> = ({ onClose, statusId }) => {
useEffect(() => {
fetchData();
getNutzappedBy(statusId);
}, []);
const onClickClose = () => {
@ -56,7 +65,7 @@ const ZapsModal: React.FC<IZapsModal> = ({ onClose, statusId }) => {
let body;
if (!zaps || !accounts) {
if (!zaps || !nutzaps || !accounts) {
body = <Spinner />;
} else {
const emptyMessage = <FormattedMessage id='status.zaps.empty' defaultMessage='No one has zapped this post yet. When someone does, they will show up here.' />;
@ -76,7 +85,7 @@ const ZapsModal: React.FC<IZapsModal> = ({ onClose, statusId }) => {
return (
<div key={index}>
<Text weight='bold'>
{shortNumberFormat(account.amount / 1000)}
{account.type === 'zap' ? shortNumberFormat(account.amount / 1000) : shortNumberFormat(account.amount)}
</Text>
<AccountContainer id={account.id} note={account.comment} emoji='⚡' />
</div>

Wyświetl plik

@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { create } from 'zustand';
import { useApi } from 'soapbox/hooks/useApi.ts';
import { Transactions, WalletData, baseWalletSchema, transactionsSchema } from 'soapbox/schemas/wallet.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';
@ -12,11 +12,13 @@ interface WalletState {
acceptsZapsCashu: boolean;
transactions: Transactions | null;
zapCashuList: string[];
nutzappedRecord: NutzappedRecord;
prevTransaction?: string | null;
nextTransaction?: string | null;
hasFetchedWallet: boolean;
hasFetchedTransactions: boolean;
setNutzappedRecord: (statusId: string, nutzappedEntry: NutzappedEntry) => void;
setAcceptsZapsCashu: (acceptsZapsCashu: boolean) => void;
setWallet: (wallet: WalletData | null) => void;
setHasFetchedWallet: (hasFetchedWallet: boolean) => void;
@ -37,9 +39,16 @@ const useWalletStore = create<WalletState>((set) => ({
prevTransaction: null,
nextTransaction: null,
zapCashuList: [],
nutzappedRecord: {},
hasFetchedWallet: false,
hasFetchedTransactions: false,
setNutzappedRecord: (statusId, nutzappedEntry) => set((state)=> ({
nutzappedRecord: {
...state.nutzappedRecord,
[statusId]: nutzappedEntry,
},
})),
setAcceptsZapsCashu: (acceptsZapsCashu) => set({ acceptsZapsCashu }),
setWallet: (wallet) => set({ wallet }),
setHasFetchedWallet: (hasFetchedWallet) => set({ hasFetchedWallet }),
@ -209,4 +218,32 @@ const useZapCashuRequest = () => {
return { zapCashuList, isLoading, error, zapCashuRequest };
};
export { useWalletStore, useWallet, useTransactions, useZapCashuRequest };
const useZappedByCashu = () => {
const api = useApi();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { setNutzappedRecord } = useWalletStore();
const getNutzappedBy = async (statusId: string) => {
setIsLoading(true);
try {
const response = await api.get(`/api/v1/ditto/cashu/statuses/${statusId}/nutzapped_by`);
// const { prev, next } = response.pagination(); // TODO: pagination after Patrick finish
const data = await response.json();
if (data) {
const normalizedData = nutzappedEntry.parse(data);
setNutzappedRecord(statusId, normalizedData);
}
} catch (err) {
const messageError = err instanceof Error ? err.message : 'Zaps not found';
toast.error('Zaps not foud');
setError(messageError);
} finally {
setIsLoading(false);
}
};
return { error, isLoading, getNutzappedBy };
};
export { useWalletStore, useWallet, useTransactions, useZapCashuRequest, useZappedByCashu };

Wyświetl plik

@ -1,6 +1,8 @@
import { NSchema as n } from '@nostrify/nostrify';
import { z } from 'zod';
import { accountSchema } from 'soapbox/schemas/account.ts';
const baseWalletSchema = z.object({
pubkey_p2pk: n.id(),
mints: z.array(z.string().url()).nonempty(),
@ -22,12 +24,30 @@ const transactionSchema = z.object({
direction: z.enum(['in', 'out']),
});
const nutzappedEntry = z.array(
z.object({
comment: z.string(),
amount: z.number(),
account: accountSchema,
}),
);
const nutzappedRecord = z.record(
z.string(),
nutzappedEntry,
);
const transactionsSchema = z.array(transactionSchema);
type NutzappedEntry = z.infer<typeof nutzappedEntry>
type NutzappedRecord = z.infer<typeof nutzappedRecord>
type Transactions = z.infer<typeof transactionsSchema>
type Quote = z.infer<typeof quoteSchema>
type WalletData = z.infer<typeof baseWalletSchema>;
export { baseWalletSchema, quoteSchema, transactionsSchema, type WalletData, type Quote, type Transactions };
export { baseWalletSchema, quoteSchema, transactionsSchema, nutzappedRecord, nutzappedEntry, type WalletData, type Quote, type Transactions, type NutzappedRecord, type NutzappedEntry };