diff --git a/src/App.tsx b/src/App.tsx index 844e0cd6..111fced9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -45,8 +45,17 @@ import { AuthProvider } from '@src/auth/context/web3Auth'; import { ResponsiveOverlay } from '@src/components/responsive-overlay'; import { Buffer } from 'buffer'; -import { Provider } from 'react-redux'; +import {Provider, useDispatch, useSelector} from 'react-redux'; import { MetaMaskProvider } from '@metamask/sdk-react'; +import {useNotifications} from "@src/hooks/use-notifications.ts"; +import {useSnackbar} from "notistack"; +import {useEffect} from "react"; +import {setGlobalNotifier} from "@notifications/internal-notifications.ts"; +import {publicClientWebSocket} from "@src/clients/viem/publicClient.ts"; +import {GLOBAL_CONSTANTS} from "@src/config-global.ts"; +import LedgerVaultAbi from "@src/config/abi/LedgerVault.json"; +import {setBlockchainEvents} from "@redux/blockchain-events"; +import {subscribeToNotifications} from "@src/utils/subscribe-notifications-supabase.ts"; window.Buffer = Buffer; @@ -103,10 +112,7 @@ export default function App() { - - - - + @@ -117,3 +123,80 @@ export default function App() { ); } + + +interface EventArgs { + recipient?: string; + origin?: string; +} +const AppContent = () => { + const dispatch = useDispatch(); + const sessionData = useSelector((state: any) => state.auth.session); + const { getNotifications } = useNotifications(); + const { enqueueSnackbar } = useSnackbar(); + + useEffect(() => { + // Set the global reference so we can call notify(...) anywhere. + setGlobalNotifier(enqueueSnackbar); + }, [enqueueSnackbar]); + + const watchEvent = (eventName: string, args: EventArgs, logText: string) => { + const results = publicClientWebSocket.watchContractEvent({ + address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, + abi: LedgerVaultAbi.abi, + eventName, + args, + onLogs: (logs) => { + console.log(logText, logs); + dispatch(setBlockchainEvents(logs)); + }, + }); + + console.log('Watching', eventName, 'events'); + console.log('Results', results); + + return results; + }; + + useEffect(() => { + if (!sessionData?.address) return; + + const events = [ + { name: 'FundsDeposited', args: { recipient: sessionData?.address }, logText: 'New deposit (user as recipient):' }, + { name: 'FundsWithdrawn', args: { origin: sessionData?.address }, logText: 'New withdraw (user as origin):' }, + { name: 'FundsTransferred', args: { origin: sessionData?.address }, logText: 'New transfer from me:' }, + { name: 'FundsTransferred', args: { recipient: sessionData?.address }, logText: 'New transfer to me:' }, + { name: 'FundsLocked', args: { account: sessionData?.address }, logText: 'New funds locked:' }, + { name: 'FundsClaimed', args: { claimer: sessionData?.address }, logText: 'New funds claimed:' }, + { name: 'FundsReserved', args: { from: sessionData?.address }, logText: 'New funds reserved:' }, + { name: 'FundsCollected', args: { from: sessionData?.address }, logText: 'New funds collected:' }, + { name: 'FundsReleased', args: { to: sessionData?.address }, logText: 'New funds released:' }, + ]; + + const unwatchers = events.map(event => watchEvent(event.name, event.args, event.logText)); + + return () => { + unwatchers.forEach(unwatch => unwatch()); + }; + }, [sessionData?.address]); + + + useEffect(() => { + if (sessionData?.profile?.id) { + // Subscribe to notifications channel + subscribeToNotifications(sessionData?.profile?.id, dispatch, ['notifications']); + + // Set the notifications in first render + getNotifications(sessionData?.profile?.id).then(() => {}); + } + }, [sessionData?.profile?.id, dispatch]); + + return ( + <> + + + + + + ) +} diff --git a/src/hooks/use-get-smart-wallet-transactions.ts b/src/hooks/use-get-smart-wallet-transactions.ts index 2a3203ca..6de24ee9 100644 --- a/src/hooks/use-get-smart-wallet-transactions.ts +++ b/src/hooks/use-get-smart-wallet-transactions.ts @@ -1,11 +1,14 @@ import { useState, useEffect } from 'react'; -import { parseAbiItem, formatUnits, Address } from 'viem'; import { useDispatch, useSelector } from 'react-redux'; +import { parseAbiItem, formatUnits, Address } from 'viem'; import { publicClient } from '@src/clients/viem/publicClient.ts'; import { GLOBAL_CONSTANTS } from '@src/config-global.ts'; import LedgerVaultAbi from '@src/config/abi/LedgerVault.json'; import { addTransaction, setTransactions } from '@redux/transactions'; +/** + * Type definition for a transaction log, including event data and relevant block/transaction metadata. + */ export type TransactionLog = { address: string; args: { @@ -29,17 +32,126 @@ export type TransactionLog = { transactionIndex: number; }; -const useGetSmartWalletTransactions = () => { +/** + * Configuration object for each event: + * - eventName: Name of the event in the smart contract ABI. + * - args: Address-related arguments used to filter logs (e.g., recipient, origin). + * - getEventType: Function to determine a custom "event type" (e.g., transferTo, transferFrom) based on the log contents and the user's address. + */ +type EventConfig = { + eventName: string; + args: Record; + getEventType: (log: any, userAddress: string) => string; +}; + +/** + * Hook to retrieve smart wallet transactions by querying logs from the LedgerVault contract. + * It also manages live updates when new events are detected in real time. + */ +export default function useGetSmartWalletTransactions() { const dispatch = useDispatch(); const sessionData = useSelector((state: any) => state.auth.session); const blockchainEvents = useSelector((state: any) => state.blockchainEvents.events); const transactions = useSelector((state: any) => state.transactions.transactions); - // Local states for loading and error const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // Function to fetch historical logs + /** + * We define all event configurations that we want to capture. + * Each configuration includes: + * - The event name. + * - An object "args" that indicates which fields in the log must match the user's address. + * - A function to map the raw event to a custom "event type" (transferFrom, transferTo, deposit, etc.). + */ + const eventConfigs: EventConfig[] = [ + { + eventName: 'FundsTransferred', + args: { recipient: sessionData?.address || '' }, + getEventType: (log, userAddress) => + log.args.origin === userAddress ? 'transferTo' : 'transferFrom', + }, + { + eventName: 'FundsTransferred', + args: { origin: sessionData?.address || '' }, + getEventType: (log, userAddress) => + log.args.origin === userAddress ? 'transferTo' : 'transferFrom', + }, + { + eventName: 'FundsDeposited', + args: { recipient: sessionData?.address || '' }, + getEventType: () => 'deposit', + }, + { + eventName: 'FundsWithdrawn', + args: { origin: sessionData?.address || '' }, + getEventType: () => 'withdraw', + }, + { + eventName: 'FundsLocked', + args: { account: sessionData?.address || '' }, + getEventType: () => 'locked', + }, + { + eventName: 'FundsClaimed', + args: { claimer: sessionData?.address || '' }, + getEventType: () => 'claimed', + }, + { + eventName: 'FundsReserved', + args: { from: sessionData?.address || '' }, + getEventType: () => 'reserved', + }, + { + eventName: 'FundsCollected', + args: { from: sessionData?.address || '' }, + getEventType: () => 'collected', + }, + { + eventName: 'FundsReleased', + args: { to: sessionData?.address || '' }, + getEventType: () => 'released', + }, + ]; + + /** + * Helper function to create the event signature needed by viem's parseAbiItem(). + * For example, an event signature looks like "event FundsTransferred(address indexed origin, address indexed recipient, ...)". + */ + const createEventSignature = (event: any): string => { + if (!event || !event.name || !event.inputs) { + throw new Error('Invalid event in ABI'); + } + const inputs = event.inputs + .map((input: any) => `${input.type}${input.indexed ? ' indexed' : ''} ${input.name}`) + .join(', '); + return `event ${event.name}(${inputs})`; + }; + + /** + * Generate a dictionary (object) of parsed ABIs based on all unique event names in the eventConfigs. + * a) Extract unique event names (e.g. FundsTransferred, FundsDeposited, etc.). + * b) Find those events in the LedgerVaultAbi and parse them with parseAbiItem(). + */ + const uniqueEventNames = Array.from( + new Set(eventConfigs.map((config) => config.eventName)) // Removes duplicates + ); + + const parsedAbis = uniqueEventNames.reduce((acc, eventName) => { + const eventAbi = LedgerVaultAbi.abi.find( + (item: any) => item.type === 'event' && item.name === eventName + ); + if (!eventAbi) { + throw new Error(`No definition found for event ${eventName} in the ABI`); + } + acc[eventName] = parseAbiItem(createEventSignature(eventAbi)); + return acc; + }, {} as Record>); + + /** + * Function to fetch historical logs from the LedgerVault contract, using the user's address as a filter. + * The logs are then sorted, processed, and stored in Redux. + */ const fetchLogs = async () => { if (!sessionData?.address) { setLoading(false); @@ -50,181 +162,112 @@ const useGetSmartWalletTransactions = () => { setLoading(true); setError(null); - // Define ABI for events to monitor - const eventsAbi = { - FundsTransferred: parseAbiItem( - createEventSignature( - LedgerVaultAbi.abi.find( - (item: any) => item.type === 'event' && item.name === 'FundsTransferred' - ) - ) - ), - FundsDeposited: parseAbiItem( - createEventSignature( - LedgerVaultAbi.abi.find( - (item: any) => item.type === 'event' && item.name === 'FundsDeposited' - ) - ) - ), - FundsWithdrawn: parseAbiItem( - createEventSignature( - LedgerVaultAbi.abi.find( - (item: any) => item.type === 'event' && item.name === 'FundsWithdrawn' - ) - ) - ), - }; - - // Fetch logs for each event - const [transfersToMe, transfersFromMe, deposits, withdraws] = await Promise.all([ - publicClient.getLogs({ - address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address, - event: eventsAbi.FundsTransferred as any, - args: { recipient: sessionData.address }, - fromBlock: 0n, - toBlock: 'latest', - }), - publicClient.getLogs({ + // a) Build an array of promises, one for each eventConfig, calling publicClient.getLogs. + const promises = eventConfigs.map(({ eventName, args }) => { + return publicClient.getLogs({ address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address, - event: eventsAbi.FundsTransferred as any, - args: { origin: sessionData.address }, + event: parsedAbis[eventName] as any, + args, fromBlock: 0n, toBlock: 'latest', - }), - publicClient.getLogs({ - address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address, - event: eventsAbi.FundsDeposited as any, - args: { recipient: sessionData.address }, - fromBlock: 0n, - toBlock: 'latest', - }), - publicClient.getLogs({ - address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address, - event: eventsAbi.FundsWithdrawn as any, - args: { origin: sessionData.address }, - fromBlock: 0n, - toBlock: 'latest', - }), - ]); + }); + }); + + // b) Execute all the promises in parallel. + const results = await Promise.all(promises); - const allLogs = [...transfersToMe, ...transfersFromMe, ...deposits, ...withdraws]; + // c) Flatten the array of arrays of logs into one array. + const allLogs = results.flat(); - // Add timestamps and format details for each log + // d) Fetch block timestamps for each log and map them to a structured format. const logsWithDetails = await Promise.all( allLogs.map(async (log: any) => { const block = await publicClient.getBlock({ blockNumber: log.blockNumber }); - // Determine the event type - const event = (() => { - switch (log.eventName) { - case 'FundsTransferred': - return log.args.origin === sessionData.address ? 'transferTo' : 'transferFrom'; - case 'FundsDeposited': - return 'deposit'; - case 'FundsWithdrawn': - return 'withdraw'; - default: - return 'unknown'; - } - })(); + // Find the event config to determine the custom "eventType". + const foundConfig = eventConfigs.find((c) => c.eventName === log.eventName); + const eventType = foundConfig + ? foundConfig.getEventType(log, sessionData?.address) + : 'unknown'; return { ...log, - timestamp: block.timestamp, // UNIX timestamp of the block - readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), // Human-readable date - formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', // Convert amount from wei to ether - event, + timestamp: block.timestamp, + readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), + formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', + event: eventType, }; }) ); - // Sort logs by block number and transaction index + // e) Sort logs by blockNumber descending, then by transactionIndex descending. const sortedLogs = logsWithDetails.sort((a, b) => { const blockDifference = Number(b.blockNumber) - Number(a.blockNumber); if (blockDifference !== 0) return blockDifference; return Number(b.transactionIndex) - Number(a.transactionIndex); }); - // Dispatch the setTransactions action to store the logs in Redux + // Finally, update Redux state with the sorted logs. dispatch(setTransactions(sortedLogs)); } catch (err) { console.error('Error fetching logs:', err); - setError(err instanceof Error ? err.message : 'An unknown error occurred'); + setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } }; - // Helper function to create event signatures - const createEventSignature = (event: any): string => { - if (!event || !event.name || !event.inputs) { - throw new Error('Invalid event in ABI'); - } - const inputs = event.inputs - .map((input: any) => `${input.type}${input.indexed ? ' indexed' : ''} ${input.name}`) - .join(', '); - return `event ${event.name}(${inputs})`; - }; - - // Effect to fetch historical logs when the address changes + /** + * useEffect hook that fires when the user's address changes, triggering the fetchLogs function. + * If there's already a list of transactions, we can stop showing the loader. + */ useEffect(() => { fetchLogs(); - - // Disable loader if data is already available if (transactions.length) { setLoading(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessionData?.address]); - // Effect to handle real-time events from blockchainEvents + /** + * Real-time events handling: + * When a new event is picked up in `blockchainEvents`, we check if it's from the LedgerVault contract and + * if it's one of the recognized event names. If yes, we process it similarly (fetch block info, add extra fields) + * and then dispatch addTransaction to Redux. + */ useEffect(() => { - if (!blockchainEvents || blockchainEvents.length === 0) return; + if (!blockchainEvents?.length) return; - // Iterate over new blockchain events blockchainEvents.forEach(async (log: any) => { - // Process only relevant events + // Filter out logs not from our contract or event names not in use. if ( log.address !== GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS || - !['FundsTransferred', 'FundsDeposited', 'FundsWithdrawn'].includes(log.eventName) + !uniqueEventNames.includes(log.eventName) ) { return; } try { const block = await publicClient.getBlock({ blockNumber: log.blockNumber }); + const foundConfig = eventConfigs.find((c) => c.eventName === log.eventName); + const eventType = foundConfig + ? foundConfig.getEventType(log, sessionData?.address) + : 'unknown'; - // Determine the event type - const event = (() => { - switch (log.eventName) { - case 'FundsTransferred': - return log.args.origin === sessionData.address ? 'transferTo' : 'transferFrom'; - case 'FundsDeposited': - return 'deposit'; - case 'FundsWithdrawn': - return 'withdraw'; - default: - return 'unknown'; - } - })(); - - // Create a formatted transaction log const formattedLog = { ...log, - timestamp: block.timestamp, // UNIX timestamp of the block - readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), // Human-readable date - formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', // Convert amount from wei to ether - event, + timestamp: block.timestamp, + readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), + formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', + event: eventType, }; - // Dispatch the addTransaction action to add the new log to Redux dispatch(addTransaction(formattedLog)); } catch (err) { console.error('Error processing real-time log:', err); } }); - }, [blockchainEvents, sessionData?.address, dispatch]); + }, [blockchainEvents, sessionData?.address, dispatch, uniqueEventNames, eventConfigs]); return { transactions, loading, error }; -}; - -export default useGetSmartWalletTransactions; +} diff --git a/src/routes/sections/index.tsx b/src/routes/sections/index.tsx index 5fd497ef..de09be1d 100644 --- a/src/routes/sections/index.tsx +++ b/src/routes/sections/index.tsx @@ -1,96 +1,9 @@ import { Navigate, useRoutes } from 'react-router-dom'; -import { GLOBAL_CONSTANTS, PATH_AFTER_LOGIN } from '@src/config-global'; +import { PATH_AFTER_LOGIN } from '@src/config-global'; import { dashboardRoutes } from './dashboard'; import NotFoundPage from '../../pages/404'; -import { useEffect } from 'react'; -import { subscribeToNotifications } from '@src/utils/subscribe-notifications-supabase.ts'; -import { useDispatch, useSelector } from 'react-redux'; -import { useNotifications } from '@src/hooks/use-notifications.ts'; -import { publicClientWebSocket } from '@src/clients/viem/publicClient.ts'; -import LedgerVaultAbi from '@src/config/abi/LedgerVault.json'; -import { setBlockchainEvents } from '@redux/blockchain-events'; -import { setGlobalNotifier } from '@notifications/internal-notifications.ts'; -import { useSnackbar } from 'notistack'; - export default function Router() { - const dispatch = useDispatch(); - const sessionData = useSelector((state: any) => state.auth.session); - const { getNotifications } = useNotifications(); - const { enqueueSnackbar } = useSnackbar(); - - useEffect(() => { - // Set the global reference so we can call notify(...) anywhere. - setGlobalNotifier(enqueueSnackbar); - }, [enqueueSnackbar]); - - useEffect(() => { - if (!sessionData?.address) return; - - // FundsDeposited (when i am the recipient) - const unwatchDeposit = publicClientWebSocket.watchContractEvent({ - address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, - abi: LedgerVaultAbi.abi, - eventName: 'FundsDeposited', - args: { recipient: sessionData?.address }, - onLogs: (logs) => { - console.log('New deposit (user as recipient):', logs); - dispatch(setBlockchainEvents(logs)); - }, - }); - - // FundsWithdrawn (when i am the origin) - const unwatchWithdraw = publicClientWebSocket.watchContractEvent({ - address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, - abi: LedgerVaultAbi.abi, - eventName: 'FundsWithdrawn', - args: { origin: sessionData?.address }, - onLogs: (logs) => { - console.log('New withdraw (user as origin):', logs); - dispatch(setBlockchainEvents(logs)); - }, - }); - - // FundsTransferred (when I send) - const unwatchTransferFrom = publicClientWebSocket.watchContractEvent({ - address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, - abi: LedgerVaultAbi.abi, - eventName: 'FundsTransferred', - args: { origin: sessionData?.address }, - onLogs: (logs) => { - console.log('New transfer from me:', logs); - dispatch(setBlockchainEvents(logs)); - }, - }); - - // FundsTransferred (when I receive) - const unwatchTransferTo = publicClientWebSocket.watchContractEvent({ - address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS, - abi: LedgerVaultAbi.abi, - eventName: 'FundsTransferred', - args: { recipient: sessionData?.address }, - onLogs: (logs) => { - console.log('New transfer to me:', logs); - dispatch(setBlockchainEvents(logs)); - }, - }); - - return () => { - unwatchDeposit(); - unwatchWithdraw(); - unwatchTransferFrom(); - unwatchTransferTo(); - }; - }, [sessionData?.address]); - - useEffect(() => { - if (sessionData?.profile?.id) { - // Subscribe to notifications channel - subscribeToNotifications(sessionData?.profile?.id, dispatch, ['notifications']); - // Set the notifications in first render - getNotifications(sessionData?.profile?.id).then(() => {}); - } - }, [sessionData?.profile?.id, dispatch]); return useRoutes([ { diff --git a/src/sections/finance/components/finance-transactions-history.tsx b/src/sections/finance/components/finance-transactions-history.tsx index fc9847d9..4ef33ad0 100644 --- a/src/sections/finance/components/finance-transactions-history.tsx +++ b/src/sections/finance/components/finance-transactions-history.tsx @@ -30,7 +30,11 @@ import useGetSmartWalletTransactions from '@src/hooks/use-get-smart-wallet-trans import { processTransactionData } from '@src/utils/finance-graphs/groupedTransactions'; import FinanceOverlayLoader from '@src/sections/finance/components/finance-overlay-loader.tsx'; -const STATUS_OPTIONS = [{ value: 'all', label: 'All' }, ...TRANSACTIONS_TYPES.slice(0, -2)]; +const STATUS_OPTIONS = [ + { value: 'all', label: 'All' }, + ...TRANSACTIONS_TYPES, + { value: 'other', label: 'Other' } +]; const TABLE_HEAD = [ { id: 'name', label: 'Transaction Info', width: 20 }, @@ -88,6 +92,10 @@ export default function FinanceTransactionsHistory() { [handleFilters] ); + const removeDuplicatesById = (array: any[]) => { + return Array.from(new Map(array.map((item) => [item.id, item])).values()); + } + return ( <> - {tab.value === 'all' && transactionData.length} + {tab.value === 'all' && + removeDuplicatesById(transactionData).length + } {tab.value === 'transferFrom' && - transactionData.filter( - (t) => - t.type.toLowerCase() === 'transferto' || t.type.toLowerCase() === 'withdraw' - ).length} + removeDuplicatesById( + transactionData.filter( + (t) => + t.type.toLowerCase() === 'transferto' || + t.type.toLowerCase() === 'withdraw' + ) + ).length + } {tab.value === 'transferTo' && - transactionData.filter( - (t) => - t.type.toLowerCase() === 'transferfrom' || - t.type.toLowerCase() === 'deposit' - ).length} + removeDuplicatesById( + transactionData.filter( + (t) => + t.type.toLowerCase() === 'transferfrom' || + t.type.toLowerCase() === 'deposit' + ) + ).length + } + {tab.value === 'other' && + removeDuplicatesById( + transactionData.filter( + (t) => + t.type.toLowerCase() === 'locked' || + t.type.toLowerCase() === 'claimed' || + t.type.toLowerCase() === 'reserved' || + t.type.toLowerCase() === 'collected' || + t.type.toLowerCase() === 'released' + ) + ).length + } } /> @@ -224,7 +254,23 @@ function applyFilter({ (t) => t.type.toLowerCase() === 'transferfrom' || t.type.toLowerCase() === 'deposit' ); } + + if (status === 'other') { + filteredData = filteredData.filter( + (t) => + t.type.toLowerCase() === 'locked' || + t.type.toLowerCase() === 'claimed' || + t.type.toLowerCase() === 'reserved' || + t.type.toLowerCase() === 'collected' || + t.type.toLowerCase() === 'released' + ); + } } + // delete duplicated items + filteredData = Array.from( + new Map(filteredData.map((item) => [item.id, item])).values() + ); + return filteredData; } diff --git a/src/sections/finance/components/finance-transactions-table-row.tsx b/src/sections/finance/components/finance-transactions-table-row.tsx index 349b0eb5..46e434bf 100644 --- a/src/sections/finance/components/finance-transactions-table-row.tsx +++ b/src/sections/finance/components/finance-transactions-table-row.tsx @@ -22,16 +22,37 @@ type Props = { // ---------------------------------------------------------------------- const urlTxBase = 'https://www.oklink.com/es-la/amoy/tx/'; +const COLORS = { + success: '#00AB55', + danger: '#FF4842', + warning: '#dc9f00', + info: '#3a7dd5', +} + +const TX_COLORS: any = { + 'transferTo': COLORS.danger, + 'transferFrom': COLORS.success, + 'deposit': COLORS.success, + 'withdraw': COLORS.danger, + 'locked': COLORS.info, + 'claimed': COLORS.success, + 'reserved': COLORS.warning, + 'collected': COLORS.success, + 'released': COLORS.success, +} // ---------------------------------------------------------------------- export default function FinanceTransactionTableRow({ row, selected }: Props) { - const { date, name, amount, avatarUrl, message, category, id } = row; + const { date, name, amount, avatarUrl, message, category, id, type } = row; const dateObject = new Date(Number(date) * 1000); const dateLbl = format(dateObject, 'dd/MM/yyyy'); const timeLbl = format(dateObject, 'p'); + console.log('row') + console.log(row) + const renderPrimary = ( @@ -61,7 +82,7 @@ export default function FinanceTransactionTableRow({ row, selected }: Props) { - + {category === 'income' ? '' : '-'} {amount} MMC diff --git a/src/sections/user/profile-header.tsx b/src/sections/user/profile-header.tsx index d877a4d2..bb598af0 100644 --- a/src/sections/user/profile-header.tsx +++ b/src/sections/user/profile-header.tsx @@ -56,8 +56,6 @@ import BadgeVerified from "@src/components/user-item/BadgeVerified.tsx"; const urlToShare = 'https://app.watchit.movie/profileId'; const urlAttestationBase = 'https://polygon-amoy.easscan.org/attestation/view/'; -// const GeoAddress = '0xEFBBD14082cF2FbCf5Badc7ee619F0f4e36D0A5B' - const shareLinks = [ { icon: 'mingcute:social-x-line', diff --git a/src/types/transaction.ts b/src/types/transaction.ts index 2502d64a..861cd929 100644 --- a/src/types/transaction.ts +++ b/src/types/transaction.ts @@ -7,6 +7,4 @@ export type IOrderTableFilters = { export const TRANSACTIONS_TYPES = [ { value: 'transferTo', label: 'Income' }, { value: 'transferFrom', label: 'Outcomes' }, - { value: 'deposit', label: 'Deposit' }, - { value: 'withdraw', label: 'Withdraw' }, ]; diff --git a/src/utils/finance-graphs/groupedTransactions.ts b/src/utils/finance-graphs/groupedTransactions.ts index 15149a76..13240f77 100644 --- a/src/utils/finance-graphs/groupedTransactions.ts +++ b/src/utils/finance-graphs/groupedTransactions.ts @@ -115,19 +115,79 @@ export type ProcessedTransactionData = { amount: string | null; }; +type EventName = + | 'transferFrom' + | 'transferTo' + | 'deposit' + | 'withdraw' + | 'locked' + | 'claimed' + | 'reserved' + | 'collected' + | 'released'; + +type EventConfig = { + getName: (args: any) => string; + getAvatarUrl: (args: any) => string; +}; + +const eventConfig: Record = { + transferFrom: { + getName: (args) => args.origin, + getAvatarUrl: (args) => dicebear(args.origin), + }, + transferTo: { + getName: (args) => args.recipient, + getAvatarUrl: (args) => dicebear(args.recipient), + }, + deposit: { + getName: (args) => args.recipient, + getAvatarUrl: (args) => dicebear(args.recipient), + }, + withdraw: { + getName: (args) => args.origin, + getAvatarUrl: (args) => dicebear(args.origin), + }, + locked: { + getName: (args) => args.account, + getAvatarUrl: (args) => dicebear(args.account), + }, + claimed: { + getName: (args) => args.claimer, + getAvatarUrl: (args) => dicebear(args.claimer), + }, + reserved: { + getName: (args) => args.from, + getAvatarUrl: (args) => dicebear(args.from), + }, + collected: { + getName: (args) => args.from, + getAvatarUrl: (args) => dicebear(args.from), + }, + released: { + getName: (args) => args.to, + getAvatarUrl: (args) => dicebear(args.to), + }, +}; + export const processTransactionData = (data: TransactionLog[]): ProcessedTransactionData[] => { - return data?.map((transaction, _index) => ({ - id: transaction.transactionHash, - name: - transaction.event === 'transferFrom' ? transaction.args.origin : transaction.args.recipient, - avatarUrl: dicebear(transaction.event === 'transferFrom' ? transaction.args.origin : transaction.args.recipient), - type: transaction.event, - message: parseTransactionTypeLabel(transaction.event), - category: parseTransactionType(transaction.event), - date: transaction.timestamp, - status: 'completed', - amount: transaction.formattedAmount, - })); + return data.map((transaction) => { + const config = eventConfig[transaction.event as EventName]; + const name = config ? config.getName(transaction.args) : 'Unknown'; + const avatarUrl = config ? config.getAvatarUrl(transaction.args) : dicebear('default'); + + return { + id: transaction.transactionHash, + name, + avatarUrl, + type: transaction.event, + message: parseTransactionTypeLabel(transaction.event), + category: parseTransactionType(transaction.event), + date: transaction.timestamp, + status: 'completed', + amount: transaction.formattedAmount, + }; + }); }; const parseTransactionTypeLabel = (type: string): string => { @@ -140,6 +200,16 @@ const parseTransactionTypeLabel = (type: string): string => { return 'Deposited'; case 'withdraw': return 'Withdraw'; + case 'locked': + return 'Locked'; + case 'claimed': + return 'Claimed'; + case 'reserved': + return 'Reserved'; + case 'collected': + return 'Collected'; + case 'released': + return 'Released'; default: return type; @@ -157,6 +227,16 @@ const parseTransactionType = (type: string): string => { return 'income'; case 'withdraw': return 'outcome'; + case 'locked': + return 'other'; + case 'claimed': + return 'other'; + case 'reserved': + return 'other'; + case 'collected': + return 'other'; + case 'released': + return 'other'; default: return type;