diff --git a/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxExecuteStatus.tsx b/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxExecuteStatus.tsx index 43be58a94..8119f2c1d 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxExecuteStatus.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxExecuteStatus.tsx @@ -13,7 +13,7 @@ import React, { useEffect, useMemo, useState } from 'react'; const DialogTxExecuteStatus = ({ chainID }: { chainID: string }) => { const { getChainInfo, getDenomInfo } = useGetChainInfo(); - const { explorerTxHashEndpoint } = getChainInfo(chainID); + const { explorerTxHashEndpoint, chainName } = getChainInfo(chainID); const { decimals = 0, displayDenom = '', @@ -54,7 +54,11 @@ const DialogTxExecuteStatus = ({ chainID }: { chainID: string }) => { src={txResponse?.code === 0 ? TXN_SUCCESS_ICON : TXN_FAILED_ICON} height={60} width={60} - alt={txResponse?.code === 0 ? 'Transaction Successful' : 'Transaction Failed'} + alt={ + txResponse?.code === 0 + ? 'Transaction Successful' + : 'Transaction Failed' + } />
@@ -62,6 +66,7 @@ const DialogTxExecuteStatus = ({ chainID }: { chainID: string }) => { explorer={explorerTxHashEndpoint || ''} txHash={txResponse?.transactionHash || ''} txSuccess={txResponse?.code === 0} + chainName={chainName} />
diff --git a/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxInstantiateStatus.tsx b/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxInstantiateStatus.tsx index 43a9282ce..6787cba05 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxInstantiateStatus.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxInstantiateStatus.tsx @@ -13,7 +13,7 @@ import React, { useEffect, useMemo, useState } from 'react'; const DialogTxInstantiateStatus = ({ chainID }: { chainID: string }) => { const { getChainInfo, getDenomInfo } = useGetChainInfo(); - const { explorerTxHashEndpoint } = getChainInfo(chainID); + const { explorerTxHashEndpoint, chainName } = getChainInfo(chainID); const { decimals = 0, displayDenom = '', @@ -54,7 +54,11 @@ const DialogTxInstantiateStatus = ({ chainID }: { chainID: string }) => { src={txResponse?.code === 0 ? TXN_SUCCESS_ICON : TXN_FAILED_ICON} height={60} width={60} - alt={txResponse?.code === 0 ? 'Transaction Successful' : 'Transaction Failed'} + alt={ + txResponse?.code === 0 + ? 'Transaction Successful' + : 'Transaction Failed' + } />
@@ -62,6 +66,7 @@ const DialogTxInstantiateStatus = ({ chainID }: { chainID: string }) => { explorer={explorerTxHashEndpoint || ''} txHash={txResponse?.transactionHash || ''} txSuccess={txResponse?.code === 0} + chainName={chainName} />
diff --git a/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxUploadCodeStatus.tsx b/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxUploadCodeStatus.tsx index 1cc26ab14..9780a8c52 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxUploadCodeStatus.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/tx-status/DialogTxUploadCodeStatus.tsx @@ -13,7 +13,7 @@ import React, { useEffect, useMemo, useState } from 'react'; const DialogTxUploadCodeStatus = ({ chainID }: { chainID: string }) => { const { getChainInfo, getDenomInfo } = useGetChainInfo(); - const { explorerTxHashEndpoint } = getChainInfo(chainID); + const { explorerTxHashEndpoint, chainName } = getChainInfo(chainID); const { decimals = 0, displayDenom = '', @@ -54,7 +54,11 @@ const DialogTxUploadCodeStatus = ({ chainID }: { chainID: string }) => { src={txResponse?.code === 0 ? TXN_SUCCESS_ICON : TXN_FAILED_ICON} height={60} width={60} - alt={txResponse?.code === 0 ? 'Transaction Successful' : 'Transaction Failed'} + alt={ + txResponse?.code === 0 + ? 'Transaction Successful' + : 'Transaction Failed' + } />
@@ -62,6 +66,7 @@ const DialogTxUploadCodeStatus = ({ chainID }: { chainID: string }) => { explorer={explorerTxHashEndpoint || ''} txHash={txResponse?.transactionHash || ''} txSuccess={txResponse?.code === 0} + chainName={chainName} />
diff --git a/frontend/src/app/(routes)/transactions/history/SearchTransaction.tsx b/frontend/src/app/(routes)/transactions/history/SearchTransaction.tsx new file mode 100644 index 000000000..a026ed911 --- /dev/null +++ b/frontend/src/app/(routes)/transactions/history/SearchTransaction.tsx @@ -0,0 +1,100 @@ +import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks'; +import { getAnyChainTransaction } from '@/store/features/recent-transactions/recentTransactionsSlice'; +import React, { useEffect, useState } from 'react'; +import SearchTransactionHash from './[network]/components/SearchTransactionHash'; +import { TxStatus } from '@/types/enums'; +import { parseTxnData } from '@/utils/util'; +import Transaction from './[network]/components/Transaction'; +import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; +import { CircularProgress } from '@mui/material'; + +const SearchTransaction = () => { + const dispatch = useAppDispatch(); + const { getChainInfo } = useGetChainInfo(); + const [searchQuery, setSearchQuery] = useState(''); + + const handleSearchQueryChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + }; + + const handleClearSearch = () => { + setSearchQuery(''); + }; + + const onSearch = (e: React.FormEvent) => { + e.preventDefault(); + if (searchQuery) dispatch(getAnyChainTransaction({ txhash: searchQuery })); + }; + + const loading = useAppSelector( + (state) => state.recentTransactions.txn?.status + ); + + const txnResult = useAppSelector( + (state) => state.recentTransactions.txn?.data?.[0] + ); + const { txHash = '' } = txnResult ? parseTxnData(txnResult) : {}; + + const [chainID, setChainID] = useState(''); + const { chainName } = getChainInfo(chainID); + + useEffect(() => { + if (txnResult) { + setChainID(txnResult.chain_id); + } + }, [txnResult]); + + return ( +
+
+ + + + + {loading === TxStatus.PENDING ? ( +
+ +
+ Fetching transaction info +
+
+ ) : ( + <> + {txnResult && chainID && chainName ? ( + + ) : null} + + )} + +
+ ); +}; + +export default SearchTransaction; + +const TransactionNotFound = () => { + const txSearchError = useAppSelector( + (state) => state.recentTransactions.txn.error + ); + + if (txSearchError) + return ( +
+

+ Sorry, the transaction you're looking for is not found. +

+
+ ); + return <>; +}; diff --git a/frontend/src/app/(routes)/transactions/history/[network]/components/SearchTransactionHash.tsx b/frontend/src/app/(routes)/transactions/history/[network]/components/SearchTransactionHash.tsx index f4229ab13..755a1cf0e 100644 --- a/frontend/src/app/(routes)/transactions/history/[network]/components/SearchTransactionHash.tsx +++ b/frontend/src/app/(routes)/transactions/history/[network]/components/SearchTransactionHash.tsx @@ -4,25 +4,38 @@ import React from 'react'; const SearchTransactionHash = ({ searchQuery, handleSearchQueryChange, + handleClearSearch, }: { searchQuery: string; handleSearchQueryChange: (e: React.ChangeEvent) => void; + handleClearSearch?: () => void; }) => { return (
- +
+ + {searchQuery && handleClearSearch && ( + + )} +
); }; -export default SearchTransactionHash; \ No newline at end of file +export default SearchTransactionHash; diff --git a/frontend/src/app/(routes)/transactions/history/[network]/components/Transaction.tsx b/frontend/src/app/(routes)/transactions/history/[network]/components/Transaction.tsx index c60ac129a..1b7d341e9 100644 --- a/frontend/src/app/(routes)/transactions/history/[network]/components/Transaction.tsx +++ b/frontend/src/app/(routes)/transactions/history/[network]/components/Transaction.tsx @@ -8,7 +8,7 @@ import NumberFormat from '@/components/common/NumberFormat'; import { get } from 'lodash'; import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; import TxMsg from './TxMsg'; -import { parseTxnData } from '@/utils/util'; +import { getTxnURL, parseTxnData } from '@/utils/util'; import { buildMessages } from '@/utils/transaction'; import { txRepeatTransaction } from '@/store/features/recent-transactions/recentTransactionsSlice'; import DialogLoader from '@/components/common/DialogLoader'; @@ -18,15 +18,17 @@ const Transaction = ({ chainName, hash, chainID, + isSearchPage = false, }: { chainName: string; hash: string; chainID: string; + isSearchPage?: boolean; }) => { const dispatch = useAppDispatch(); const { getChainInfo, getDenomInfo } = useGetChainInfo(); const basicChainInfo = getChainInfo(chainID); - const { chainLogo } = basicChainInfo; + const { chainLogo, explorerTxHashEndpoint } = basicChainInfo; const txnRepeatStatus = useAppSelector( (state) => state.recentTransactions?.txnRepeat?.status @@ -76,6 +78,9 @@ const Transaction = ({ status={success ? 'success' : 'failed'} onRepeatTxn={onRepeatTxn} disableAction={disableAction} + goBackUrl={`/transactions/history/${chainName.toLowerCase()}`} + isSearchPage={isSearchPage} + mintscanURL={getTxnURL(explorerTxHashEndpoint, hash || '')} />
@@ -91,7 +96,7 @@ const Transaction = ({ alt="network-logo" className="w-6 h-6" /> -

{chainName}

+

{chainName}

diff --git a/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionCard.tsx b/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionCard.tsx index ce3c75707..6190ea760 100644 --- a/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionCard.tsx +++ b/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionCard.tsx @@ -1,10 +1,10 @@ import React from 'react'; import Copy from '@/components/common/Copy'; -import { getTypeURLName, parseTxnData, shortenString } from '@/utils/util'; +import { parseTxnData, shortenString } from '@/utils/util'; import NewTxnMsg from '@/components/NewTxnMsg'; import Link from 'next/link'; import { useAppDispatch } from '@/custom-hooks/StateHooks'; -import { buildMessages } from '@/utils/transaction'; +import { buildMessages, formattedMsgType } from '@/utils/transaction'; import { txRepeatTransaction } from '@/store/features/recent-transactions/recentTransactionsSlice'; import CustomButton from '@/components/common/CustomButton'; import TxnTimeStamp from './TxnTimeStamp'; @@ -39,7 +39,7 @@ const TransactionCard = ({

{shortenString(txHash, 24)}

@@ -50,7 +50,7 @@ const TransactionCard = ({ {txn?.messages?.map((msg, index) => (
- {getTypeURLName(msg?.['@type'])} + {formattedMsgType(msg?.['@type'])}
))} diff --git a/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHeader.tsx b/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHeader.tsx index 76f6eed74..d59306318 100644 --- a/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHeader.tsx +++ b/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHeader.tsx @@ -1,8 +1,9 @@ import React from 'react'; import Image from 'next/image'; import Copy from '@/components/common/Copy'; -import { useRouter } from 'next/navigation'; import CustomButton from '@/components/common/CustomButton'; +import Link from 'next/link'; +import { REDIRECT_ICON } from '@/constants/image-names'; type Status = 'success' | 'failed'; @@ -11,6 +12,9 @@ interface TransactionHeaderProps { hash: string; onRepeatTxn: () => void; disableAction: boolean; + goBackUrl: string; + isSearchPage?: boolean; + mintscanURL: string; } const TransactionHeader: React.FC = ({ @@ -18,19 +22,23 @@ const TransactionHeader: React.FC = ({ hash, disableAction, onRepeatTxn, + goBackUrl, + isSearchPage, + mintscanURL, }) => { - const router = useRouter(); const isSuccess = status === 'success'; const textColorClass = isSuccess ? 'text-[#2BA472]' : 'text-[#FA5E42]'; return (
- + {isSearchPage ? null : ( + + Go back + + )}
-
+
= ({
- +
+ {isSearchPage ? null : ( + + )} + {mintscanURL ? ( + +
View on Mintscan
+ View Proposal + + ) : null} +
diff --git a/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHistoryDashboard.tsx b/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHistoryDashboard.tsx index 5cfb7c4b5..86808ceb7 100644 --- a/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHistoryDashboard.tsx +++ b/frontend/src/app/(routes)/transactions/history/[network]/components/TransactionHistoryDashboard.tsx @@ -7,12 +7,18 @@ import TransactionCard from './TransactionCard'; import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; import DialogLoader from '@/components/common/DialogLoader'; import { TxStatus } from '@/types/enums'; +import { Pagination } from '@mui/material'; +import { paginationComponentStyles } from '@/utils/commonStyles'; +import TxnsLoading from '../../loaders/TxnsLoading'; + +const ITEMS_PER_PAGE = 5; const TransactionHistoryDashboard = ({ chainID }: { chainID: string }) => { const { getChainInfo, getDenomInfo } = useGetChainInfo(); const { displayDenom, decimals, minimalDenom } = getDenomInfo(chainID); const basicChainInfo = getChainInfo(chainID); + const [currentPage, setCurrentPage] = useState(1); const [searchQuery, setSearchQuery] = useState(''); const txnHistory = useGetTransactions({ chainID }); @@ -29,6 +35,9 @@ const TransactionHistoryDashboard = ({ chainID }: { chainID: string }) => { const txnRepeatStatus = useAppSelector( (state) => state.recentTransactions?.txnRepeat?.status ); + const txnsLoading = useAppSelector( + (state) => state.recentTransactions.txns.status + ); const loading = txnRepeatStatus === TxStatus.PENDING; const currency = { @@ -37,6 +46,22 @@ const TransactionHistoryDashboard = ({ chainID }: { chainID: string }) => { coinMinimalDenom: minimalDenom, }; + const handlePageChange = (value: number) => { + const offset = (value - 1) * ITEMS_PER_PAGE; + txnHistory.fetchTransactions(ITEMS_PER_PAGE, offset); + setCurrentPage(value); + }; + + const totalCount = useAppSelector( + (state) => state.recentTransactions?.txns?.total + ); + + const pagesCount = Math.ceil(totalCount / ITEMS_PER_PAGE); + const showPagination = + transactions?.length && + txnsLoading !== TxStatus.PENDING && + pagesCount !== 0; + return (
@@ -55,6 +80,20 @@ const TransactionHistoryDashboard = ({ chainID }: { chainID: string }) => { /> ))}
+ {txnsLoading === TxStatus.PENDING ? : null} + {showPagination ? ( +
+ { + handlePageChange(value); + }} + /> +
+ ) : null}
); diff --git a/frontend/src/app/(routes)/transactions/history/[network]/components/TxMsg.tsx b/frontend/src/app/(routes)/transactions/history/[network]/components/TxMsg.tsx index 11ee7aa36..035311ea4 100644 --- a/frontend/src/app/(routes)/transactions/history/[network]/components/TxMsg.tsx +++ b/frontend/src/app/(routes)/transactions/history/[network]/components/TxMsg.tsx @@ -217,7 +217,7 @@ const TxMsg: React.FC = ({ msg, expandedIndex, chainID, toggleExpand return (
-
toggleExpand(mIndex)}> +
toggleExpand(mIndex)}>

{getTypeURLName(msg?.['@type']) || msg?.['@type']}

{ + return ( +
+ {Array(3) + .fill(null) + .map((index) => ( +
+
+
+

+

+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ ); +}; + +export default TxnsLoading; diff --git a/frontend/src/app/(routes)/transactions/history/page.tsx b/frontend/src/app/(routes)/transactions/history/page.tsx index 835aa98df..9059133bb 100644 --- a/frontend/src/app/(routes)/transactions/history/page.tsx +++ b/frontend/src/app/(routes)/transactions/history/page.tsx @@ -2,53 +2,36 @@ import EmptyScreen from '@/components/common/EmptyScreen'; import PageHeader from '@/components/common/PageHeader'; import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks'; -import { setChangeNetworkDialogOpen } from '@/store/features/common/commonSlice'; import { setConnectWalletOpen } from '@/store/features/wallet/walletSlice'; -import React, { useEffect } from 'react'; +import React from 'react'; +import SearchTransaction from './SearchTransaction'; const Page = () => { const dispatch = useAppDispatch(); const isWalletConnected = useAppSelector((state) => state.wallet.connected); - const openChangeNetwork = () => { - dispatch(setChangeNetworkDialogOpen({ open: true, showSearch: true })); - }; const connectWalletOpen = () => { dispatch(setConnectWalletOpen(true)); }; - useEffect(() => { - if (isWalletConnected) { - openChangeNetwork(); - } - }, []); - return (
-
-
- {isWalletConnected ? ( - - ) : ( - - )} -
+
+ {isWalletConnected ? ( + + ) : ( + + )}
); diff --git a/frontend/src/components/NewTxnMsg.tsx b/frontend/src/components/NewTxnMsg.tsx index f5aab5b1a..462d8aed3 100644 --- a/frontend/src/components/NewTxnMsg.tsx +++ b/frontend/src/components/NewTxnMsg.tsx @@ -1,229 +1,270 @@ import { - DELEGATE_TYPE_URL, - DEPOSIT_TYPE_URL, - IBC_SEND_TYPE_URL, - MAP_TXN_TYPES, - MSG_AUTHZ_EXEC, - MSG_AUTHZ_GRANT, - MSG_AUTHZ_REVOKE, - REDELEGATE_TYPE_URL, - SEND_TYPE_URL, - UNDELEGATE_TYPE_URL, - VOTE_OPTIONS, - VOTE_TYPE_URL, + DELEGATE_TYPE_URL, + DEPOSIT_TYPE_URL, + IBC_SEND_TYPE_URL, + MAP_TXN_TYPES, + MSG_AUTHZ_EXEC, + MSG_AUTHZ_GRANT, + MSG_AUTHZ_REVOKE, + MSG_GRANT_ALLOWANCE, + MSG_REVOKE_ALLOWANCE, + REDELEGATE_TYPE_URL, + SEND_TYPE_URL, + UNDELEGATE_TYPE_URL, + VOTE_OPTIONS, + VOTE_TYPE_URL, } from '@/utils/constants'; import { - capitalizeFirstLetter, - getTypeURLName, - parseAmount, - shortenAddress, + getTypeURLName, + parseAmount, + shortenAddress, + shortenString, } from '@/utils/util'; import { get } from 'lodash'; import React from 'react'; const NewTxnMsg = ({ - msgs, - currency, - failed, + msgs, + currency, + failed, }: { - msgs: NewMsg[]; - currency: Currency; - failed: boolean; + msgs: NewMsg[]; + currency: Currency; + failed: boolean; }) => { - const status = failed ? 'failed' : 'successfully'; + const status = failed ? 'failed' : 'successfully'; - if (!msgs.length) { - return null - } + if (!msgs.length) { + return null; + } - const msgType = msgs[0]?.typeUrl || get(msgs, '[0][@type]', ''); - const txTypeText = msgs?.length - ? failed - ? 'while ' + MAP_TXN_TYPES[msgType]?.[1] + ' to' - : MAP_TXN_TYPES[msgType]?.[0] + ' to' - : ''; - return ( - <> - {msgs?.length ? ( -
- {msgType === SEND_TYPE_URL ? ( -
-
- {parseAmount(msgs[0]?.amount, currency)}{' '} - {status} {txTypeText} -
- -
- - {shortenAddress(msgs[0]?.toAddress || '', 15) || '-'} - -
-
- -
- ) : null} - {msgType === DELEGATE_TYPE_URL ? ( -
-
- {parseAmount([msgs[0]?.amount], currency)}{' '} - {status} {txTypeText} -
- -
- - {msgs[0]?.validator_address || '-'} - - -
-
- -
- ) : null} - {msgType === UNDELEGATE_TYPE_URL ? ( -
-
- {parseAmount([msgs[0]?.amount], currency)}{' '} - {status}{' '} - - {failed - ? 'while ' + MAP_TXN_TYPES[msgType][1] + ' from' - : MAP_TXN_TYPES[msgType][0] + 'from'} - -
- -
- - {shortenAddress( - msgs[0]?.validator_address || '', - 15 - ) || '-'} - -
-
- -
- ) : null} - {msgType === REDELEGATE_TYPE_URL ? ( -
-
- {parseAmount([msgs[0]?.amount], currency)}{' '} - {status}{' '} - - {failed - ? 'while ' + MAP_TXN_TYPES[msgType][1] - : MAP_TXN_TYPES[msgType][0]} - -
- -
- ) : null} - {msgType === VOTE_TYPE_URL ? ( -
-
- {capitalizeFirstLetter(status)}{' '} - - {failed - ? 'while ' + MAP_TXN_TYPES[msgType][1] - : MAP_TXN_TYPES[msgType][0]} - - {VOTE_OPTIONS[get(msgs, '[0].option', 0) - 1]} - on - proposal #{parseInt(get(msgs, '[0].proposal_id', ''))} -
- -
- ) : null} - {msgType === DEPOSIT_TYPE_URL ? ( -
-
- {parseAmount(msgs[0]?.amount, currency)}{' '} - {status}{' '} - - {failed - ? 'while ' + MAP_TXN_TYPES[msgType][1] - : MAP_TXN_TYPES[msgType][0]} - - on proposal #{parseInt(msgs[0]?.proposal_id)} -
- -
- ) : null} - {msgType === IBC_SEND_TYPE_URL ? ( -
-
- {parseAmount([msgs[0]?.token], currency)}{' '} - {status} {txTypeText} -
- -
- - {shortenAddress(msgs[0]?.receiver || '', 15) || '-'} - -
-
- -
- ) : null} - {msgType === MSG_AUTHZ_REVOKE ? ( -
-
- {status}
- -
- {'revoked authz from '} - {shortenAddress(get(msgs, '[0].grantee', '') || '', 15) || '-'} - -
-
- -
- ) : null} - {msgType === MSG_AUTHZ_EXEC ? ( -
-
- {status} Executed
- -
- - { - get(msgs, '[0].msgs', []).map((m, mindex) => ( -
{getTypeURLName(get(m, '@type'))}
- )) - } -
-
-
- -
- ) : null} - {msgType === MSG_AUTHZ_GRANT ? ( -
-
- {status} granted to
- -
- - {shortenAddress(get(msgs, '[0].grantee', '') || '', 15) || '-'} - -
-
- -
- ) : null} + const msgType = msgs[0]?.typeUrl || get(msgs, '[0][@type]', ''); + const txTypeText = msgs?.length + ? failed + ? 'while ' + MAP_TXN_TYPES[msgType]?.[1] + ' to' + : MAP_TXN_TYPES[msgType]?.[0] + ' to' + : ''; + return ( + <> + {msgs?.length ? ( +
+ {msgType === SEND_TYPE_URL ? ( +
+
+ {parseAmount(msgs[0]?.amount, currency)}{' '} + {status} {txTypeText} +
+ +
+ + {shortenAddress(msgs[0]?.to_address || '', 15) || '-'} + +
+
+ +
+ ) : null} + {msgType === DELEGATE_TYPE_URL ? ( +
+
+ {parseAmount([msgs[0]?.amount], currency)}{' '} + {status} {txTypeText} +
+ +
+ + {shortenString(msgs[0]?.validator_address, 24) || '-'} + +
+
+ +
+ ) : null} + {msgType === UNDELEGATE_TYPE_URL ? ( +
+
+ {parseAmount([msgs[0]?.amount], currency)}{' '} + {status}{' '} + + {failed + ? 'while ' + MAP_TXN_TYPES[msgType][1] + ' from' + : MAP_TXN_TYPES[msgType][0] + 'from'} + +
+ +
+ + {shortenString(msgs[0]?.validator_address || '', 24) || '-'} + +
+
+ +
+ ) : null} + {msgType === REDELEGATE_TYPE_URL ? ( +
+
+ {parseAmount([msgs[0]?.amount], currency)}{' '} + {status}{' '} + + {failed + ? 'while ' + MAP_TXN_TYPES[msgType][1] + : MAP_TXN_TYPES[msgType][0]} + +
+ +
+ ) : null} + {msgType === VOTE_TYPE_URL ? ( +
+
+ {status}{' '} + + {failed + ? 'while ' + MAP_TXN_TYPES[msgType][1] + : MAP_TXN_TYPES[msgType][0]} + + {VOTE_OPTIONS[get(msgs, '[0].option', 0) - 1]} + on + + proposal #{parseInt(get(msgs, '[0].proposal_id', ''))} + +
+ +
+ ) : null} + {msgType === DEPOSIT_TYPE_URL ? ( +
+
+ {parseAmount(msgs[0]?.amount, currency)}{' '} + {status}{' '} + + {failed + ? 'while ' + MAP_TXN_TYPES[msgType][1] + : MAP_TXN_TYPES[msgType][0]} + + on proposal #{parseInt(msgs[0]?.proposal_id)} +
+ +
+ ) : null} + {msgType === IBC_SEND_TYPE_URL ? ( +
+
+ {parseAmount([msgs[0]?.token], currency)}{' '} + {status} {txTypeText} +
+ +
+ + {shortenAddress(msgs[0]?.receiver || '', 15) || '-'} + +
+
+ +
+ ) : null} + {msgType === MSG_AUTHZ_GRANT ? ( +
+
+ {status} granted to{' '} +
+ +
+ + {shortenAddress(get(msgs, '[0].grantee', '') || '', 15) || + '-'} + +
+
+ +
+ ) : null} + {msgType === MSG_AUTHZ_REVOKE ? ( +
+
+ {status}{' '} +
+ +
+ + {' '} + {'revoked authz from '} + {shortenAddress(get(msgs, '[0].grantee', '') || '', 15) || + '-'} + +
+
+ +
+ ) : null} + {msgType === MSG_AUTHZ_EXEC ? ( +
+
+ {status} Executed{' '} +
+ +
+ + {get(msgs, '[0].msgs', []).map((m, mindex) => ( +
+ Authz {getTypeURLName(get(m, '@type'))} +
+ ))} +
+
+
+ +
+ ) : null} + {msgType === MSG_GRANT_ALLOWANCE ? ( +
+
+ {status} granted allowance + to{' '} +
+ +
+ + {shortenAddress(get(msgs, '[0].grantee', '') || '', 15) || + '-'} + +
+
+ +
+ ) : null} + {msgType === MSG_REVOKE_ALLOWANCE ? ( +
+
+ {status} revoked allowance + from{' '} +
+ +
+ + {shortenAddress(get(msgs, '[0].grantee', '') || '', 15) || + '-'} +
- ) : null} - - ); +
+ +
+ ) : null} +
+ ) : null} + + ); }; export default NewTxnMsg; const MoreMessages = ({ msgs }: { msgs: NewMsg[] }) => { - return ( - <> - {msgs.length > 1 ? ( - +{msgs.length - 1} - ) : null} - - ); + return ( + <> + {msgs.length > 1 ? ( + +{msgs.length - 1} + ) : null} + + ); }; diff --git a/frontend/src/components/txn-status-popups/TransactionStatusPopup.tsx b/frontend/src/components/txn-status-popups/TransactionStatusPopup.tsx index c1facf2fd..da8ef5067 100644 --- a/frontend/src/components/txn-status-popups/TransactionStatusPopup.tsx +++ b/frontend/src/components/txn-status-popups/TransactionStatusPopup.tsx @@ -8,7 +8,7 @@ import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; import { getRecentTransactions } from '@/store/features/recent-transactions/recentTransactionsSlice'; import { dialogBoxPaperPropStyles } from '@/utils/commonStyles'; import { IBC_SEND_TYPE_URL, SEND_TYPE_URL } from '@/utils/constants'; -import { getTxnURL, shortenMsg } from '@/utils/util'; +import { getTxnURL, getTxnURLOnResolute, shortenMsg } from '@/utils/util'; import { Dialog, DialogContent } from '@mui/material'; import Image from 'next/image'; import Link from 'next/link'; @@ -29,7 +29,7 @@ const TransactionStatusPopup = () => { const [isOpen, setIsOpen] = useState(false); const dispatch = useAppDispatch(); const { getChainInfo, getDenomInfo } = useGetChainInfo(); - const { explorerTxHashEndpoint = '' } = tx?.chainID + const { explorerTxHashEndpoint = '', chainName = '' } = tx?.chainID ? getChainInfo(tx.chainID) : {}; const { @@ -129,8 +129,8 @@ const TransactionStatusPopup = () => { /> { return (
@@ -26,7 +28,7 @@ const TxnStatus = ({ diff --git a/frontend/src/store/features/recent-transactions/recentTransactionsService.tsx b/frontend/src/store/features/recent-transactions/recentTransactionsService.tsx index 1d770ec03..6c905af65 100644 --- a/frontend/src/store/features/recent-transactions/recentTransactionsService.tsx +++ b/frontend/src/store/features/recent-transactions/recentTransactionsService.tsx @@ -14,11 +14,10 @@ const ALL_TXNS_URL = ( offset: number ) => `/txns/${chainID}/${address}?limit=${limit}&offset=${offset}`; -const TXN_URL = ( - address: string, - chainID: string, - txhash: string, -) => `/txns/${chainID}/${address}/${txhash}`; +const TXN_URL = (address: string, chainID: string, txhash: string) => + `/txns/${chainID}/${address}/${txhash}`; + +const ANY_CHAIN_TX_URL = (txHash: string) => `/search/txns/${txHash}`; export const fetchRecentTransactions = ({ payload, @@ -56,8 +55,12 @@ export const fetchTx = ({ }): Promise => Axios.get(`${BASE_URL}${TXN_URL(address, chainID, txhash)}`); +export const fetchAnyChainTx = (txHash: string): Promise => + Axios.get(`${BASE_URL}${ANY_CHAIN_TX_URL(txHash)}`); + export default { recentTransactions: fetchRecentTransactions, allTransactions: fetchAllTransactions, - fetchTx: fetchTx + fetchTx: fetchTx, + fetchAnyChainTx, }; diff --git a/frontend/src/store/features/recent-transactions/recentTransactionsSlice.tsx b/frontend/src/store/features/recent-transactions/recentTransactionsSlice.tsx index cde00212c..b694a3d0b 100644 --- a/frontend/src/store/features/recent-transactions/recentTransactionsSlice.tsx +++ b/frontend/src/store/features/recent-transactions/recentTransactionsSlice.tsx @@ -1,7 +1,7 @@ 'use client'; import { TxStatus } from '@/types/enums'; -import { ERR_UNKNOWN } from '@/utils/errors'; +import { ERR_TXN_NOT_FOUND, ERR_UNKNOWN } from '@/utils/errors'; import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { AxiosError } from 'axios'; import recentTransactionsService from './recentTransactionsService'; @@ -31,7 +31,7 @@ interface RecentTransactionsState { data?: ParsedTransaction[]; status: TxStatus; error: string; - } + }; } const initialState: RecentTransactionsState = { @@ -49,7 +49,7 @@ const initialState: RecentTransactionsState = { data: undefined, status: TxStatus.INIT, error: '', - } + }, }; export const getRecentTransactions = createAsyncThunk( @@ -182,9 +182,7 @@ export const getTransaction = createAsyncThunk( try { const response = await recentTransactionsService.fetchTx(data); let txns: ParsedTransaction[] = []; - const txnsData = response?.data?.data?.data - console.log('dddddddddddddddddddd', txnsData) - // txnsData.forEach((txn: ParsedTransaction) => { + const txnsData = response?.data?.data?.data; const { txhash, messages } = txnsData; if (messages[0]?.['@type'] === IBC_SEND_TYPE_URL) { const ibcTx = getIBCTxn(txhash); @@ -218,7 +216,7 @@ export const getTransaction = createAsyncThunk( // total: response?.data?.data?.total, // }, // }; - return { data: txns } + return { data: txns }; } catch (error) { if (error instanceof AxiosError) return rejectWithValue({ @@ -229,6 +227,39 @@ export const getTransaction = createAsyncThunk( } ); +export const getAnyChainTransaction = createAsyncThunk( + 'recent-txns/get-any-chain-txn', + async ( + data: { + txhash: string; + }, + { rejectWithValue } + ) => { + try { + const response = await recentTransactionsService.fetchAnyChainTx( + data.txhash + ); + let txns: ParsedTransaction[] = []; + const txnsData = response?.data?.data?.data; + let formattedTxn = txnsData; + if (txnsData) { + formattedTxn = { ...formattedTxn, isIBCPending: false }; + formattedTxn = { ...formattedTxn, isIBCTxn: false }; + } else { + throw new Error(ERR_TXN_NOT_FOUND); + } + + txns = [...txns, formattedTxn]; + + return { data: txns }; + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (error: any) { + const errMsg = error?.message || ERR_TXN_NOT_FOUND; + return rejectWithValue(errMsg); + } + } +); + export const txRepeatTransaction = createAsyncThunk( 'recent-txns/repeat-txn', async ( @@ -243,7 +274,8 @@ export const txRepeatTransaction = createAsyncThunk( data.messages, GAS_FEE, '', - `${data.basicChainInfo.feeAmount * 10 ** data.basicChainInfo.decimals}${data.basicChainInfo.feeCurrencies[0].coinDenom + `${data.basicChainInfo.feeAmount * 10 ** data.basicChainInfo.decimals}${ + data.basicChainInfo.feeCurrencies[0].coinDenom }`, data.basicChainInfo.rest, data?.feegranter?.length ? data.feegranter : undefined, @@ -364,6 +396,21 @@ export const recentTransactionsSlice = createSlice({ const payload = action.payload as { message: string }; state.txns.error = payload.message || ''; }); + builder + .addCase(getAnyChainTransaction.pending, (state) => { + state.txn.status = TxStatus.PENDING; + state.txn.error = ''; + }) + .addCase(getAnyChainTransaction.fulfilled, (state, action) => { + state.txn.status = TxStatus.IDLE; + state.txn.data = action?.payload?.data || []; + state.txns.error = ''; + }) + .addCase(getAnyChainTransaction.rejected, (state, action) => { + state.txn.status = TxStatus.REJECTED; + state.txn.data = []; + state.txn.error = action.error.message || 'Failed to fetch transaction'; + }); builder .addCase(txRepeatTransaction.pending, (state) => { state.txnRepeat.status = TxStatus.PENDING; diff --git a/frontend/src/txns/authz/revoke.ts b/frontend/src/txns/authz/revoke.ts index 66d598c71..1664f2c0c 100644 --- a/frontend/src/txns/authz/revoke.ts +++ b/frontend/src/txns/authz/revoke.ts @@ -1,6 +1,6 @@ import { MsgRevoke } from 'cosmjs-types/cosmos/authz/v1beta1/tx'; -const msgAuthzRevokeTypeUrl = '/cosmos.authz.v1beta1.MsgRevoke'; +export const msgAuthzRevokeTypeUrl = '/cosmos.authz.v1beta1.MsgRevoke'; export function AuthzRevokeMsg( granter: string, diff --git a/frontend/src/txns/feegrant/revoke.ts b/frontend/src/txns/feegrant/revoke.ts index 1b8e3c77a..89ca4b9cd 100644 --- a/frontend/src/txns/feegrant/revoke.ts +++ b/frontend/src/txns/feegrant/revoke.ts @@ -1,6 +1,6 @@ import { MsgRevokeAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx"; -const revokeTypeUrl = "/cosmos.feegrant.v1beta1.MsgRevokeAllowance"; +export const revokeTypeUrl = "/cosmos.feegrant.v1beta1.MsgRevokeAllowance"; export function FeegrantRevokeMsg(granter: string, grantee: string): Msg { return { diff --git a/frontend/src/utils/commonStyles.ts b/frontend/src/utils/commonStyles.ts index 41bec37b1..46b02787e 100644 --- a/frontend/src/utils/commonStyles.ts +++ b/frontend/src/utils/commonStyles.ts @@ -74,7 +74,7 @@ export const multiSelectDropDownStyle = { export const customSelectStyles = { '& .MuiOutlinedInput-input': { color: '#fffffff0', - fontSize: '14px' + fontSize: '14px', }, '& .MuiOutlinedInput-root': { padding: '0px !important', @@ -92,5 +92,30 @@ export const customSelectStyles = { fontWeight: 200, }, borderRadius: '100px', - height: '40px' + height: '40px', +}; + +export const paginationComponentStyles = { + '& .MuiPaginationItem-page': { + '&:hover': { + backgroundColor: '#FFFFFF05', + }, + fontSize: '12px', + minWidth: '24px', + height: '24px', + borderRadius: '4px', + color: '#ffffff80', + fontWeight: '200', + }, + '& .Mui-selected': { + backgroundColor: '#FFFFFF05', + fontWeight: '600', + color: '#ffffff', + }, + '& .MuiPaginationItem-icon': { + color: '#fff', + }, + '& .MuiPaginationItem-ellipsis, & .MuiPaginationItem-ellipsisIcon': { + color: 'white', + }, }; diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 9cb5e1edc..8538acf9a 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -58,6 +58,9 @@ export const VOTE_TYPE_URL = '/cosmos.gov.v1beta1.MsgVote'; export const MSG_AUTHZ_REVOKE = '/cosmos.authz.v1beta1.MsgRevoke'; export const MSG_AUTHZ_EXEC = '/cosmos.authz.v1beta1.MsgExec'; export const MSG_AUTHZ_GRANT = '/cosmos.authz.v1beta1.MsgGrant'; +export const MSG_GRANT_ALLOWANCE = '/cosmos.feegrant.v1beta1.MsgGrantAllowance'; +export const MSG_REVOKE_ALLOWANCE = + '/cosmos.feegrant.v1beta1.MsgRevokeAllowance'; export const MULTI_TRANSFER_MSG_COUNT = 3; export const DELETE_TXN_DIALOG_IMAGE_PATH = '/delete-txn-popup-image.png'; diff --git a/frontend/src/utils/errors.ts b/frontend/src/utils/errors.ts index fe70daaab..4e5fd6ba6 100644 --- a/frontend/src/utils/errors.ts +++ b/frontend/src/utils/errors.ts @@ -33,3 +33,4 @@ export const PERMISSION_NOT_SELECTED_ERROR = 'Atleast one permission must be selected'; export const FAILED_TO_FETCH = 'Failed to fetch'; export const NETWORK_ERROR = 'Network error'; +export const ERR_TXN_NOT_FOUND = 'TXN not found'; \ No newline at end of file diff --git a/frontend/src/utils/transaction.ts b/frontend/src/utils/transaction.ts index b60951c74..fb8523360 100644 --- a/frontend/src/utils/transaction.ts +++ b/frontend/src/utils/transaction.ts @@ -45,6 +45,8 @@ import { serializeMsgDeposit, } from '@/txns/gov/deposit'; import { voteOptionNumber, voteOptions } from './constants'; +import { msgAuthzRevokeTypeUrl } from '@/txns/authz/revoke'; +import { revokeTypeUrl } from '@/txns/feegrant/revoke'; export function NewTransaction( txResponse: ParsedTxResponse, @@ -232,8 +234,12 @@ export const formattedMsgType = (msgType: string) => { return 'Exec Authz'; case msgAuthzGrantTypeUrl: return 'Grant Authz'; + case msgAuthzRevokeTypeUrl: + return 'Revoke Authz'; case msgFeegrantGrantTypeUrl: return 'Grant Allowance'; + case revokeTypeUrl: + return 'Revoke Allowance'; case msgVoteTypeUrl: return 'Vote'; case msgDepositTypeUrl: diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 62a3b7801..4dfdb01ca 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -487,6 +487,13 @@ export const getTxnURL = ( return cleanURL(explorerTxHashEndpoint) + '/' + hash; }; +export const getTxnURLOnResolute = ( + chainName: string, + hash: string +): string => { + return `/transactions/history/${chainName.toLowerCase()}/${hash}`; +}; + export const parseAmount = (amount: Coin[], currency: Currency) => { return formatCoin( parseBalance(amount, currency.coinDecimals, currency.coinMinimalDenom),