Implement transactions

merge-requests/3333/head
danidfra 2025-03-21 00:19:16 -03:00
rodzic e04f7086f8
commit c3c87c8451
4 zmienionych plików z 64 dodań i 85 usunięć

Wyświetl plik

@ -28,7 +28,7 @@ import toast from 'soapbox/toast.tsx';
const messages = defineMessages({
balance: { id: 'wallet.balance.cashu', defaultMessage: '{amount} sats' },
balance: { id: 'wallet.sats', defaultMessage: '{amount} sats' },
withdraw: { id: 'wallet.balance.withdraw_button', defaultMessage: 'Withdraw' },
exchange: { id: 'wallet.balance.exchange_button', defaultMessage: 'Exchange' },
mint: { id: 'wallet.balance.mint_button', defaultMessage: 'Mint' },

Wyświetl plik

@ -1,19 +1,19 @@
import withdrawIcon from '@tabler/icons/outline/cash.svg';
import arrowBarDownIcon from '@tabler/icons/outline/arrow-bar-down.svg';
import arrowBarUpIcon from '@tabler/icons/outline/arrow-bar-up.svg';
import moreIcon from '@tabler/icons/outline/dots-circle-horizontal.svg';
import infoIcon from '@tabler/icons/outline/info-circle.svg';
import questionIcon from '@tabler/icons/outline/question-mark.svg';
import exchangeIcon from '@tabler/icons/outline/transfer.svg';
import trendingDown from '@tabler/icons/outline/trending-down.svg';
import trendingUp from '@tabler/icons/outline/trending-up.svg';
import { useEffect } from 'react';
import { FormattedDate, defineMessages, useIntl } from 'react-intl';
// import IconButton from 'soapbox/components/ui/icon-button.tsx';
import Button from 'soapbox/components/ui/button.tsx';
import Divider from 'soapbox/components/ui/divider.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import IconButton from 'soapbox/components/ui/icon-button.tsx';
import Spinner from 'soapbox/components/ui/spinner.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { useCashu } from 'soapbox/features/zap/hooks/useCashu.ts';
import { useApi } from 'soapbox/hooks/useApi.ts';
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
const themes = {
@ -25,38 +25,36 @@ const themes = {
success: '!text-success-600',
inherit: '!text-inherit',
white: '!text-white',
exchange: '!text-blue-600 dark:!text-blue-300',
withdraw: '!text-orange-600 dark:!text-orange-300',
};
const capitaliza = (str: string) => {
return str.charAt(0).toLocaleUpperCase() + str.slice(1);
};
const transaction = (e: { type: string; amount: number; from?: string | null; to?: string | null; mint?: string | null; date: string }, lastElementIndex: number, index: number) => {
const messages = defineMessages({
amount: { id: 'wallet.sats', defaultMessage: '{amount} sats' },
more: { id: 'wallet.transactions.more', defaultMessage: 'More' },
});
const TransactionItem = (e: { amount: number; created_at: number ; direction: 'in' | 'out' }, lastElementIndex: number, index: number) => {
const intl = useIntl();
const isLastElement = index === lastElementIndex;
let icon, sla, messageColor: typeof themes[keyof typeof themes] | undefined ;
const { type, amount, date } = e;
switch (type) {
case 'received':
icon = trendingUp;
sla = e.from;
let icon, type, messageColor;
const { direction, amount, created_at } = e;
const formattedDate = <FormattedDate value={new Date(created_at * 1000)} hour12 year='numeric' month='short' day='2-digit' hour='numeric' minute='2-digit' />;
switch (direction) {
case 'in':
icon = arrowBarDownIcon;
type = 'Received';
messageColor = themes.success;
break;
case 'sent':
icon = trendingDown;
sla = e.to;
case 'out':
icon = arrowBarUpIcon;
type = 'Sent';
messageColor = themes.danger;
break;
case 'exchanged':
case 'withdrawn':
icon = e.type === 'exchanged' ? exchangeIcon : withdrawIcon;
sla = e.mint || 'MoonFileMoney';
messageColor = e.type === 'exchanged' ? themes.exchange : themes.withdraw;
break;
default:
messageColor = 'default';
type = 'Unknown';
icon = questionIcon;
}
@ -71,30 +69,22 @@ const transaction = (e: { type: string; amount: number; from?: string | null; t
<Stack justifyContent='center'>
<Text size='lg'>
{sla}
</Text>
<Text theme='muted' size='xs'>
{capitaliza(type)}
{type}
</Text>
</Stack>
</HStack>
<HStack space={2} alignItems='center'>
<Stack alignItems='end' justifyContent='center'>
<HStack space={1}>
<Text size='lg'>
{amount}
</Text>
<Text size='lg'>
{/* sats */}
</Text>
</HStack>
<Text size='lg'>
{intl.formatMessage(messages.amount, { amount: amount })}
</Text>
<Text theme='muted' size='xs'>
{date}
{formattedDate}
</Text>
</Stack>
<IconButton src={infoIcon} theme='secondary' />
{/* <IconButton src={infoIcon} theme='secondary' /> */}
</HStack>
</HStack>
@ -103,60 +93,31 @@ const transaction = (e: { type: string; amount: number; from?: string | null; t
);
};
const eG = [
{
to: null,
from: 'Patrick',
type: 'received',
mint: null,
amount: 100,
date: '02/11/24',
},
{
to: 'Alex',
from: null,
type: 'sent',
mint: null,
amount: 50,
date: '02/09/24',
},
{
to: null,
from: null,
type: 'exchanged',
mint: 'MoonFileMoney',
amount: 200,
date: '02/07/24',
},
{
to: null,
from: null,
type: 'withdrawn',
mint: 'MoonFileMoney',
amount: 300,
date: '02/05/24',
},
];
const Transactions = () => {
const intl = useIntl();
const api = useApi();
const { account } = useOwnAccount();
const { transactions, getTransactions } = useCashu();
useEffect(
() => {
getTransactions(api);
}
, []);
if (!account) {
return null;
}
const lastItem = eG.length - 1;
return (
<Stack className='rounded-lg p-3' alignItems='center' space={4}>
<Stack space={2} className='w-full'>
{eG.map((item, index) => {
return transaction(item, lastItem, index);
})}
{transactions ? transactions.slice(0, 4).map((item, index) => TransactionItem(item, 3, index)) : <Spinner withText={false} />}
</Stack>
<Button icon={moreIcon} theme='primary' >
{/* More */}
{intl.formatMessage(messages.more)}
</Button>
</Stack>

Wyświetl plik

@ -2,7 +2,7 @@ import { produce } from 'immer';
import { create } from 'zustand';
import { MastodonClient } from 'soapbox/api/MastodonClient.ts';
import { WalletData, baseWalletSchema } from 'soapbox/schemas/wallet.ts';
import { Transactions, WalletData, baseWalletSchema, 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';
@ -17,17 +17,21 @@ interface ICashuState {
isLoading: boolean;
error: string | null;
wallet: WalletData | null;
transactions: Transactions | null;
createWallet: (api: MastodonClient, walletInfo: IWalletInfo) => Promise<void>;
getTransactions: (api: MastodonClient) => Promise<void>;
getWallet: (api: MastodonClient, hasMessage?: boolean) => Promise<void>;
nutzapRequest: (api: MastodonClient, account: AccountEntity, amount: number, comment: string, status?: StatusEntity) => void;
}
// TODO: Convert it into a custom hook after the backend is finished
export const useCashu = create<ICashuState>((set) => ({
nutzapsList: {},
isLoading: false,
error: null,
wallet: null,
transactions: null,
createWallet: async (api, walletInfo) => {
try {
@ -56,6 +60,19 @@ export const useCashu = create<ICashuState>((set) => ({
}
},
getTransactions: async (api) => {
try {
const response = await api.get('/api/v1/ditto/cashu/transactions');
const data: WalletData = await response.json();
if (data) {
const normalizedData = transactionsSchema.parse(data);
set({ transactions: normalizedData });
}
} catch (error) {
toast.error('Transactions not found');
}
},
nutzapRequest: async (api, account, amount, comment, status) => {
set((state) => ({ ...state, isLoading: true, error: null }));

Wyświetl plik

@ -1713,7 +1713,7 @@
"video.play": "Play",
"video.unmute": "Unmute sound",
"wallet": "Wallet",
"wallet.balance.cashu": "{amount} sats",
"wallet.sats": "{amount} sats",
"wallet.balance.exchange_button": "Exchange",
"wallet.balance.expired": "Expired",
"wallet.balance.mint.paid_message": "Your mint was successful, and your sats are now in your balance. Enjoy!",
@ -1737,6 +1737,7 @@
"wallet.relays.error": "Failed to update mints.",
"wallet.relays.sucess": "Relays updated with success!",
"wallet.transactions": "Transactions",
"wallet.transactions.more": "More",
"who_to_follow.title": "People To Follow",
"zap.button.text.rounded": "{method} {amount}K sats",
"zap.finish": "Finish",