From ed79f45df62db844a075c159743cd032a6f2f0b3 Mon Sep 17 00:00:00 2001 From: Nabarun Date: Tue, 2 Sep 2025 22:10:53 +0530 Subject: [PATCH] Integrate APIs for showing failed transactions --- pages/dashboard/failed.tsx | 432 +++++++++++++++++++++++-------- pages/dashboard/transactions.tsx | 46 +++- utils/clipboard.ts | 19 ++ 3 files changed, 380 insertions(+), 117 deletions(-) create mode 100644 utils/clipboard.ts diff --git a/pages/dashboard/failed.tsx b/pages/dashboard/failed.tsx index b807713..3b81700 100644 --- a/pages/dashboard/failed.tsx +++ b/pages/dashboard/failed.tsx @@ -1,41 +1,95 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; + import { XCircleIcon, ArrowTopRightOnSquareIcon, ChevronLeftIcon, ChevronRightIcon, + ExclamationTriangleIcon, + ClipboardDocumentIcon, } from '@heroicons/react/24/outline'; + import Layout from '../../components/Layout'; -import { mockTransactions, mockBridgeTransactions, mockSwapTransactions } from '../../data/mockData'; +import { dashboardApi, TransactionData, SwapData, ApiError } from '../../utils/api'; +import { getExplorerUrl } from '../../utils/explorer'; +import { copyToClipboard } from '../../utils/clipboard'; export default function FailedTransactions() { - const [currentPage, setCurrentPage] = useState(1); + // MTM to NYM state + const [mtmFailures, setMtmFailures] = useState([]); + const [mtmLoading, setMtmLoading] = useState(true); + const [mtmError, setMtmError] = useState(null); + const [mtmCurrentPage, setMtmCurrentPage] = useState(1); + const [mtmTotalPages, setMtmTotalPages] = useState(1); + + // ETH to NYM state + const [ethFailures, setEthFailures] = useState([]); + const [ethLoading, setEthLoading] = useState(true); + const [ethError, setEthError] = useState(null); + const [ethCurrentPage, setEthCurrentPage] = useState(1); + const [ethTotalPages, setEthTotalPages] = useState(1); + const itemsPerPage = 5; - // Get only failed transactions - const failedTransactions = [ - ...mockTransactions.filter(tx => tx.error).map(tx => ({ ...tx, type: 'MTM to NYM' as const })), - ...mockBridgeTransactions.filter(tx => tx.error).map(tx => ({ ...tx, type: 'ETH Bridge' as const })), - ...mockSwapTransactions.filter(tx => tx.error).map(tx => ({ ...tx, type: 'ETH Swap' as const })), - ].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + // Fetch MTM to NYM failed conversions + useEffect(() => { + const fetchMtmFailures = async () => { + try { + setMtmLoading(true); + setMtmError(null); + const response = await dashboardApi.getConversions({ + page: mtmCurrentPage, + limit: itemsPerPage, + status: 'failed' + }); + setMtmFailures(response.transactions); + setMtmTotalPages(response.pagination.totalPages); + } catch (error) { + console.error('Error fetching MTM failures:', error); + setMtmError(error instanceof ApiError ? error.message : 'Failed to load MTM conversion failures'); + } finally { + setMtmLoading(false); + } + }; - // Pagination logic - const totalPages = Math.ceil(failedTransactions.length / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const endIndex = startIndex + itemsPerPage; - const paginatedTransactions = failedTransactions.slice(startIndex, endIndex); + fetchMtmFailures(); + }, [mtmCurrentPage]); - const renderPagination = () => { + // Fetch ETH to NYM failed swaps/bridges + useEffect(() => { + const fetchEthFailures = async () => { + try { + setEthLoading(true); + setEthError(null); + const response = await dashboardApi.getSwaps({ + page: ethCurrentPage, + limit: itemsPerPage, + status: 'failed' + }); + setEthFailures(response.swaps); + setEthTotalPages(response.pagination.totalPages); + } catch (error) { + console.error('Error fetching ETH failures:', error); + setEthError(error instanceof ApiError ? error.message : 'Failed to load ETH conversion failures'); + } finally { + setEthLoading(false); + } + }; + + fetchEthFailures(); + }, [ethCurrentPage]); + + const renderPagination = (currentPage: number, totalPages: number, onPageChange: (page: number) => void) => { if (totalPages <= 1) return null; return (
- Showing {startIndex + 1} to {Math.min(endIndex, failedTransactions.length)} of {failedTransactions.length} results + Page {currentPage} of {totalPages}
- {/* Failed Transactions Table */} -
-
-
-
- - - - - - - - - - - {paginatedTransactions.length > 0 ? ( - paginatedTransactions.map((tx, index) => { - const txKey = `${tx.type}-${tx.id}`; - - return ( - - - + {/* MTM to NYM Conversion Failures */} +
+

MTM to NYM Conversion Failures

+
+
+
+
+
- Type - - Transaction Link - - Error Details - - Failed At -
-
- - {tx.type} -
-
+ + + + + + + + + + {mtmLoading ? ( + renderLoadingState() + ) : mtmError ? ( + renderErrorState(mtmError) + ) : mtmFailures.length > 0 ? ( + mtmFailures.map((tx, index) => ( + + - - - ); - }) - ) : ( - - - - )} - -
+ Transaction Link + + From Address + + Error Details + + Failed At +
- {'transactionHash' in tx && tx.transactionHash && ( - - View Transaction - - - )} - {'ethTransactionHash' in tx && tx.ethTransactionHash && ( - - View Transaction - - - )} + + View Solana TX + + + + {tx.fromAddress} -
-

- {tx.error} +

+

+ {tx.error && tx.error.length > 80 ? `${tx.error.substring(0, 80)}...` : (tx.error || 'Unknown error')}

+ {tx.error && ( + + )}
{new Date(tx.createdAt).toLocaleString()}
-
- - - -

No failed transactions

-

- All transactions are processing successfully. -

-
-
+ )) + ) : ( + renderEmptyState('All MTM to NYM conversions have been processed successfully.') + )} + + +
+ {renderPagination(mtmCurrentPage, mtmTotalPages, setMtmCurrentPage)} +
+ + + {/* ETH to NYM Conversion Failures */} +
+

ETH to NYM Conversion Failures

+
+
+
+
+ + + + + + + + + + + + {ethLoading ? ( + + + + ) : ethError ? ( + + + + ) : ethFailures.length > 0 ? ( + ethFailures.map((swap, index) => ( + + + + + + + + )) + ) : ( + + + + )} + +
+ ETH Amount + + Swap Transaction + + Bridge Transaction + + Error Details + + Failed At +
+
+
+

Loading failed transactions...

+
+
+
+ +

Error Loading Data

+

{ethError}

+
+
+ {parseFloat(swap.ethAmount).toFixed(4)} ETH + + {swap.transactionHash ? ( + + View Swap TX + + + ) : ( + No transaction + )} + + {swap.bridgeTransaction?.ethTransactionHash ? ( + + View Bridge TX + + + ) : ( + No bridge transaction + )} + +
+ {swap.error && ( +
+

+ Swap: {swap.error.length > 50 ? `${swap.error.substring(0, 50)}...` : swap.error} +

+ +
+ )} + {swap.bridgeTransaction?.error && ( +
+

+ Bridge: {swap.bridgeTransaction.error.length > 50 ? `${swap.bridgeTransaction.error.substring(0, 50)}...` : swap.bridgeTransaction.error} +

+ +
+ )} +
+
+ + {new Date(swap.createdAt).toLocaleString()} + +
+
+ + + +

No Failed Transactions

+

+ All ETH to NYM conversions have been processed successfully. +

+
+
+
+
+
+ {renderPagination(ethCurrentPage, ethTotalPages, setEthCurrentPage)}
- {renderPagination()}
diff --git a/pages/dashboard/transactions.tsx b/pages/dashboard/transactions.tsx index 4ae8f53..7e882e3 100644 --- a/pages/dashboard/transactions.tsx +++ b/pages/dashboard/transactions.tsx @@ -5,10 +5,12 @@ import { ArrowTopRightOnSquareIcon, ChevronLeftIcon, ChevronRightIcon, + ClipboardDocumentIcon, } from '@heroicons/react/24/outline'; import Layout from '../../components/Layout'; import dashboardApi, { TransactionData, SwapData } from '../../utils/api'; import { getExplorerUrl } from '../../utils/explorer'; +import { copyToClipboard } from '../../utils/clipboard'; export default function Transactions() { const [mtmCurrentPage, setMtmCurrentPage] = useState(1); @@ -171,8 +173,17 @@ export default function Transactions() { {tx.error ? ( -
- {tx.error} +
+
+ {tx.error} +
+
) : '-'} @@ -269,8 +280,35 @@ export default function Transactions() { {hasError ? ( -
- {swap.error || bridgeTransaction?.error || 'Unknown error'} +
+ {swap.error && ( +
+
+ Swap: {swap.error.length > 30 ? `${swap.error.substring(0, 30)}...` : swap.error} +
+ +
+ )} + {bridgeTransaction?.error && ( +
+
+ Bridge: {bridgeTransaction.error.length > 30 ? `${bridgeTransaction.error.substring(0, 30)}...` : bridgeTransaction.error} +
+ +
+ )}
) : '-'} diff --git a/utils/clipboard.ts b/utils/clipboard.ts new file mode 100644 index 0000000..faaff08 --- /dev/null +++ b/utils/clipboard.ts @@ -0,0 +1,19 @@ +/** + * Copy text to clipboard utility + */ +export const copyToClipboard = async (text: string, label: string = 'Text'): Promise => { + try { + await navigator.clipboard.writeText(text); + console.log(`${label} copied to clipboard`); + } catch (err) { + console.error('Failed to copy text: ', err); + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + console.log(`${label} copied to clipboard (fallback method)`); + } +}; \ No newline at end of file