From 87d1456622d7c0f2604604eecb52bf90636b3fd8 Mon Sep 17 00:00:00 2001 From: Nabarun Date: Tue, 2 Sep 2025 21:10:32 +0530 Subject: [PATCH 1/5] Integrate API to show bridge transactions --- .env.example | 9 +- deploy/README.md | 14 ++- deploy/deploy.sh | 3 +- pages/dashboard/index.tsx | 34 +++++- pages/dashboard/transactions.tsx | 189 +++++++++++++++++++------------ utils/api.ts | 40 +++++++ utils/explorer.ts | 17 +++ 7 files changed, 222 insertions(+), 84 deletions(-) create mode 100644 utils/explorer.ts diff --git a/.env.example b/.env.example index 50408b7..d1e1458 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ +# Public endpoint of mtm-to-nym-service NEXT_PUBLIC_MTM_SERVICE_URL=http://localhost:3000 -# Blockchain RPC Endpoints -NEXT_PUBLIC_ETH_RPC_URL= -NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net \ No newline at end of file +# NYM chain RPC endpoint +NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net + +# ETH chain RPC endpoint +ETH_RPC_URL= diff --git a/deploy/README.md b/deploy/README.md index 480d438..1b96cef 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -41,9 +41,19 @@ ```bash # Create env for deployment from example env cp ../.env.example .app.env + ``` - # Fill in the required values - nano .app.env +- Update the env variables in `.app.env` + + ```bash + # Public endpoint of mtm-to-nym-service + NEXT_PUBLIC_MTM_SERVICE_URL=https://mtm-vpn.laconic.com + + # NYM chain RPC endpoint + NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net + + # ETH chain RPC endpoint + ETH_RPC_URL= ``` ## Run diff --git a/deploy/deploy.sh b/deploy/deploy.sh index c2b7d8d..178204e 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -12,8 +12,7 @@ echo "Using AUTHORITY: $AUTHORITY" REPO_URL="https://git.vdb.to/cerc-io/mtm-vpn-dashboard" # Get the latest commit hash for a branch -# TODO: Change to main branch when ready -BRANCH_NAME="ng-deploy-laconic" +BRANCH_NAME="main" LATEST_HASH=$(git ls-remote $REPO_URL refs/heads/$BRANCH_NAME | awk '{print $1}') # Gitea diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index a17a43a..963cf60 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -8,12 +8,14 @@ import { XCircleIcon, CloudArrowDownIcon, WalletIcon, + ArrowTopRightOnSquareIcon, } from '@heroicons/react/24/outline'; import { StargateClient } from '@cosmjs/stargate'; import { Decimal } from '@cosmjs/math'; import Layout from '../../components/Layout'; import dashboardApi, { ApiError, DashboardStats, TransactionData } from '../../utils/api'; +import { getExplorerUrl } from '../../utils/explorer'; interface BalanceData { address: string; @@ -358,14 +360,36 @@ export default function Dashboard() { )}
-
- {`${transaction.transactionHash.slice(0, 8)}...${transaction.transactionHash.slice(-8)}`} +
+ + {`${transaction.transactionHash.slice(0, 8)}...${transaction.transactionHash.slice(-8)}`} + +
- {transaction.error ? - `Error: ${transaction.error.substring(0, 40)}...` : + {transaction.error ? ( + `Error: ${transaction.error.substring(0, 40)}...` + ) : transaction.nymTransactionHash ? ( +
+ NYM: + + {`${transaction.nymTransactionHash.slice(0, 8)}...${transaction.nymTransactionHash.slice(-8)}`} + + +
+ ) : ( `From: ${transaction.fromAddress.slice(0, 8)}...${transaction.fromAddress.slice(-8)}` - } + )}
diff --git a/pages/dashboard/transactions.tsx b/pages/dashboard/transactions.tsx index 8c3cce4..4ae8f53 100644 --- a/pages/dashboard/transactions.tsx +++ b/pages/dashboard/transactions.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { CheckCircleIcon, XCircleIcon, @@ -7,50 +7,68 @@ import { ChevronRightIcon, } from '@heroicons/react/24/outline'; import Layout from '../../components/Layout'; -import { mockTransactions, mockBridgeTransactions, mockSwapTransactions } from '../../data/mockData'; +import dashboardApi, { TransactionData, SwapData } from '../../utils/api'; +import { getExplorerUrl } from '../../utils/explorer'; export default function Transactions() { const [mtmCurrentPage, setMtmCurrentPage] = useState(1); const [otherCurrentPage, setOtherCurrentPage] = useState(1); + const [mtmConversions, setMtmConversions] = useState([]); + const [ethConversions, setEthConversions] = useState([]); + const [mtmTotalPages, setMtmTotalPages] = useState(1); + const [ethTotalPages, setEthTotalPages] = useState(1); + const [mtmLoading, setMtmLoading] = useState(true); + const [ethLoading, setEthLoading] = useState(true); + const [mtmError, setMtmError] = useState(null); + const [ethError, setEthError] = useState(null); const itemsPerPage = 5; - // MTM to NYM conversions (2-transaction structure: Solana + Nyx) - const mtmToNymConversions = mockTransactions.map(tx => ({ ...tx, type: 'MTM to NYM' as const })); - - // ETH to NYM conversions (each comprising of a Swap and Bridge tx) - const ethToNymConversions = mockBridgeTransactions.map((bridgeTx, index) => ({ - id: `eth-nym-${bridgeTx.id}`, - bridgeTransaction: bridgeTx, - swapTransaction: mockSwapTransactions[index] || null, // Pair with corresponding swap - createdAt: bridgeTx.createdAt, - error: bridgeTx.error || (mockSwapTransactions[index]?.error || null), - })).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); + useEffect(() => { + fetchMtmConversions(); + }, [mtmCurrentPage]); - const getExplorerUrl = (hash: string, type: 'solana' | 'ethereum' | 'nym') => { - switch (type) { - case 'solana': - return `https://explorer.solana.com/tx/${hash}`; - case 'ethereum': - return `https://etherscan.io/tx/${hash}`; - case 'nym': - return `https://explorer.nyx.net/transactions/${hash}`; - default: - return '#'; + useEffect(() => { + fetchEthConversions(); + }, [otherCurrentPage]); + + const fetchMtmConversions = async () => { + try { + setMtmLoading(true); + setMtmError(null); + const response = await dashboardApi.getConversions({ + page: mtmCurrentPage, + limit: itemsPerPage, + status: 'all' + }); + setMtmConversions(response.transactions); + setMtmTotalPages(response.pagination.totalPages); + } catch (error) { + console.error('Failed to fetch MTM conversions:', error); + setMtmError('Failed to load MTM conversions'); + } finally { + setMtmLoading(false); } }; - // Pagination logic for MTM conversions - const mtmTotalPages = Math.ceil(mtmToNymConversions.length / itemsPerPage); - const mtmStartIndex = (mtmCurrentPage - 1) * itemsPerPage; - const mtmEndIndex = mtmStartIndex + itemsPerPage; - const paginatedMtmConversions = mtmToNymConversions.slice(mtmStartIndex, mtmEndIndex); - - // Pagination logic for ETH to NYM conversions - const ethTotalPages = Math.ceil(ethToNymConversions.length / itemsPerPage); - const ethStartIndex = (otherCurrentPage - 1) * itemsPerPage; - const ethEndIndex = ethStartIndex + itemsPerPage; - const paginatedEthConversions = ethToNymConversions.slice(ethStartIndex, ethEndIndex); + const fetchEthConversions = async () => { + try { + setEthLoading(true); + setEthError(null); + const response = await dashboardApi.getSwaps({ + page: otherCurrentPage, + limit: itemsPerPage, + status: 'all' + }); + setEthConversions(response.swaps); + setEthTotalPages(response.pagination.totalPages); + } catch (error) { + console.error('Failed to fetch ETH conversions:', error); + setEthError('Failed to load ETH conversions'); + } finally { + setEthLoading(false); + } + }; const renderPagination = (currentPage: number, totalPages: number, setCurrentPage: (page: number) => void) => { if (totalPages <= 1) return null; @@ -168,19 +186,20 @@ export default function Transactions() { ); }; - const renderEthConversionRow = (conversion: any, index: number) => { - const { bridgeTransaction, swapTransaction } = conversion; + const renderEthConversionRow = (swap: SwapData, index: number) => { + const { bridgeTransaction } = swap; + const hasError = swap.error || (bridgeTransaction?.error); return ( - +
- {conversion.error ? ( + {hasError ? ( ) : ( )} - - {conversion.error ? 'Failed' : 'Success'} + + {hasError ? 'Failed' : 'Success'}
@@ -188,13 +207,13 @@ export default function Transactions() {
{/* Swap Transaction */} - {swapTransaction && ( + {swap && (
)} {/* Bridge Transaction */} -
-
- Bridge: - {bridgeTransaction.ethTransactionHash && ( - - - - )} + {bridgeTransaction && ( +
+
+ Bridge: + {bridgeTransaction.ethTransactionHash && ( + + + + )} +
+
+ {bridgeTransaction.ethTransactionHash ? `${bridgeTransaction.ethTransactionHash.slice(0, 12)}...${bridgeTransaction.ethTransactionHash.slice(-12)}` : 'Error during transaction'} +
-
- {bridgeTransaction.ethTransactionHash ? `${bridgeTransaction.ethTransactionHash.slice(0, 12)}...${bridgeTransaction.ethTransactionHash.slice(-12)}` : 'Pending...'} -
-
+ )}
- {swapTransaction && swapTransaction.ethAmount && ( + {swap.ethAmount && (
- ETH: {swapTransaction.ethAmount} + ETH: {swap.ethAmount}
)} - {bridgeTransaction.nymAmount && ( + {bridgeTransaction?.nymAmount && (
NYM: {bridgeTransaction.nymAmount}
@@ -247,16 +268,16 @@ export default function Transactions() { - {conversion.error ? ( -
- {conversion.error} + {hasError ? ( +
+ {swap.error || bridgeTransaction?.error || 'Unknown error'}
) : '-'} - {new Date(conversion.createdAt).toLocaleString()} + {new Date(swap.createdAt).toLocaleString()} @@ -307,8 +328,20 @@ export default function Transactions() { - {paginatedMtmConversions.length > 0 ? ( - paginatedMtmConversions.map((tx, index) => renderMtmConversionRow(tx, index)) + {mtmLoading ? ( + + + Loading MTM conversions... + + + ) : mtmError ? ( + + + {mtmError} + + + ) : mtmConversions.length > 0 ? ( + mtmConversions.map((tx, index) => renderMtmConversionRow(tx, index)) ) : ( @@ -355,8 +388,20 @@ export default function Transactions() { - {paginatedEthConversions.length > 0 ? ( - paginatedEthConversions.map((conversion, index) => renderEthConversionRow(conversion, index)) + {ethLoading ? ( + + + Loading ETH conversions... + + + ) : ethError ? ( + + + {ethError} + + + ) : ethConversions.length > 0 ? ( + ethConversions.map((swap, index) => renderEthConversionRow(swap, index)) ) : ( diff --git a/utils/api.ts b/utils/api.ts index d1dab74..fc86ae3 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -38,6 +38,30 @@ export interface TransactionsResponse { }; } +export interface SwapData { + id: number; + ethAmount: string; + transactionHash?: string; + error?: string | null; + createdAt: string; + bridgeTransaction?: { + id: number; + nymAmount: string; + ethTransactionHash?: string; + error?: string | null; + }; +} + +export interface SwapsResponse { + swaps: SwapData[]; + totalCount: number; + pagination: { + page: number; + limit: number; + totalPages: number; + }; +} + export class ApiError extends Error { constructor( message: string, @@ -109,6 +133,22 @@ export const dashboardApi = { return apiRequest(endpoint); }, + // Get swaps with pagination and filtering + getSwaps: (params?: { + page?: number; + limit?: number; + status?: string; + }): Promise => { + const searchParams = new URLSearchParams(); + if (params?.page) searchParams.set('page', params.page.toString()); + if (params?.limit) searchParams.set('limit', params.limit.toString()); + if (params?.status) searchParams.set('status', params.status); + + const query = searchParams.toString(); + const endpoint = `/api/swaps${query ? `?${query}` : ''}`; + return apiRequest(endpoint); + }, + }; export default dashboardApi; \ No newline at end of file diff --git a/utils/explorer.ts b/utils/explorer.ts new file mode 100644 index 0000000..0ad583f --- /dev/null +++ b/utils/explorer.ts @@ -0,0 +1,17 @@ +/** + * Generate blockchain explorer URLs for different networks + */ +export const getExplorerUrl = (hash: string, type: 'solana' | 'ethereum' | 'nym'): string => { + switch (type) { + case 'solana': + return `https://explorer.solana.com/tx/${hash}`; + case 'ethereum': + return `https://etherscan.io/tx/${hash}`; + case 'nym': + return `https://ping.pub/nyx/tx/${hash}`; + default: + return '#'; + } +}; + +export type ExplorerType = 'solana' | 'ethereum' | 'nym'; -- 2.45.2 From ed79f45df62db844a075c159743cd032a6f2f0b3 Mon Sep 17 00:00:00 2001 From: Nabarun Date: Tue, 2 Sep 2025 22:10:53 +0530 Subject: [PATCH 2/5] 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 -- 2.45.2 From a041f0d9194f0410d74c3bd4de9fe03a9975847c Mon Sep 17 00:00:00 2001 From: Nabarun Date: Tue, 2 Sep 2025 23:06:31 +0530 Subject: [PATCH 3/5] Integrate API to show app downloads --- .env.example | 3 + pages/api/github/releases.ts | 35 +++++++++ pages/dashboard/downloads.tsx | 140 ++++++++++++++++++++++++++-------- pages/dashboard/index.tsx | 18 ++++- utils/api.ts | 1 - utils/common.ts | 18 +++++ utils/downloads.ts | 76 ++++++++++++++++++ 7 files changed, 253 insertions(+), 38 deletions(-) create mode 100644 pages/api/github/releases.ts create mode 100644 utils/common.ts create mode 100644 utils/downloads.ts diff --git a/.env.example b/.env.example index d1e1458..b9cef81 100644 --- a/.env.example +++ b/.env.example @@ -6,3 +6,6 @@ NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net # ETH chain RPC endpoint ETH_RPC_URL= + +# GitHub releases API endpoint +GITHUB_RELEASES_URL=https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases diff --git a/pages/api/github/releases.ts b/pages/api/github/releases.ts new file mode 100644 index 0000000..c7ff106 --- /dev/null +++ b/pages/api/github/releases.ts @@ -0,0 +1,35 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + const githubReleasesUrl = process.env.GITHUB_RELEASES_URL; + + if (!githubReleasesUrl) { + return res.status(500).json({ error: 'GitHub releases URL not configured' }); + } + + try { + const response = await fetch(githubReleasesUrl, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'User-Agent': 'MTM-VPN-Dashboard/1.0', + }, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + res.status(200).json(data); + } catch (error) { + console.error('GitHub releases proxy error:', error); + res.status(500).json({ + error: error instanceof Error ? error.message : 'GitHub releases request failed' + }); + } +} \ No newline at end of file diff --git a/pages/dashboard/downloads.tsx b/pages/dashboard/downloads.tsx index 3ed4ed1..0726734 100644 --- a/pages/dashboard/downloads.tsx +++ b/pages/dashboard/downloads.tsx @@ -1,15 +1,40 @@ +import { useState, useEffect } from 'react'; import { CloudArrowDownIcon, DevicePhoneMobileIcon, ArrowTopRightOnSquareIcon, + ExclamationTriangleIcon, } from '@heroicons/react/24/outline'; + import Layout from '../../components/Layout'; -import { mockAppDownloads } from '../../data/mockData'; +import { ApiError } from '../../utils/api'; +import { fetchGitHubReleases, ProcessedRelease } from '../../utils/downloads'; export default function Downloads() { - // Filter to only Android apps - const androidApps = mockAppDownloads.filter(app => app.platform === 'Android'); - const totalDownloads = androidApps.reduce((sum, app) => sum + app.downloads, 0); + const [releases, setReleases] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadReleases = async () => { + try { + setLoading(true); + setError(null); + + const { releases } = await fetchGitHubReleases(); + setReleases(releases); + } catch (err) { + console.error('Failed to fetch releases:', err); + setError(err instanceof ApiError ? err.message : 'Failed to load release data'); + } finally { + setLoading(false); + } + }; + + loadReleases(); + }, []); + + const totalDownloads = releases.reduce((sum, release) => sum + release.downloads, 0); return ( @@ -37,7 +62,13 @@ export default function Downloads() { Total Downloads
- {totalDownloads.toLocaleString()} + {loading ? ( +
+ ) : error ? ( + Error + ) : ( + totalDownloads.toLocaleString() + )}
@@ -57,9 +88,6 @@ export default function Downloads() { Version - - Platform - Downloads @@ -75,50 +103,96 @@ export default function Downloads() { - {androidApps - .sort((a, b) => new Date(b.releaseDate).getTime() - new Date(a.releaseDate).getTime()) - .map((app, index) => ( - - - {app.version} + {loading ? ( + Array.from({ length: 3 }).map((_, index) => ( + + +
- - -
- - {app.platform} -
+ +
+ + +
+ + +
+ + +
+ + + )) + ) : error ? ( + + +
+ +

Error Loading Releases

+

{error}

+
+ + + ) : releases.length > 0 ? ( + releases.map((release, index) => ( + + + {release.version} - {app.downloads.toLocaleString()} + {release.downloads.toLocaleString()} - {app.fileSize} + {release.fileSize} - {app.releaseDate.toLocaleDateString()} + {release.releaseDate.toLocaleDateString()} - - View Release - - + - ))} + )) + ) : ( + + +
+ +

No Releases Found

+

+ No Android app releases are currently available. +

+
+ + + )}
diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index 963cf60..03c2e4b 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -16,6 +16,7 @@ import { Decimal } from '@cosmjs/math'; import Layout from '../../components/Layout'; import dashboardApi, { ApiError, DashboardStats, TransactionData } from '../../utils/api'; import { getExplorerUrl } from '../../utils/explorer'; +import { fetchGitHubReleases } from '../../utils/downloads'; interface BalanceData { address: string; @@ -28,6 +29,7 @@ export default function Dashboard() { const [dashboardStats, setDashboardStats] = useState(null); const [recentTransactions, setRecentTransactions] = useState([]); const [accountBalances, setAccountBalances] = useState([]); + const [totalDownloads, setTotalDownloads] = useState(0); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -39,10 +41,11 @@ export default function Dashboard() { try { setIsLoading(true); - // Fetch dashboard data (without balances) - const [statsData, conversionsData] = await Promise.allSettled([ + // Fetch all dashboard data including downloads + const [statsData, conversionsData, downloadsData] = await Promise.allSettled([ dashboardApi.getStats(), - dashboardApi.getConversions({ limit: 5, status: 'all' }) + dashboardApi.getConversions({ limit: 5, status: 'all' }), + fetchGitHubReleases() ]); // Handle dashboard stats @@ -57,6 +60,13 @@ export default function Dashboard() { console.error('Failed to fetch dashboard stats:', statsData.reason); } + // Handle downloads data separately + if (downloadsData.status === 'fulfilled') { + setTotalDownloads(downloadsData.value.totalDownloads); + } else { + console.error('Failed to fetch downloads data:', downloadsData.reason); + } + // Handle recent transactions if (conversionsData.status === 'fulfilled') { setRecentTransactions(conversionsData.value.transactions); @@ -177,7 +187,7 @@ export default function Dashboard() { { id: 4, name: 'Total Downloads', - stat: dashboardStats.totalDownloads.toLocaleString(), + stat: totalDownloads.toLocaleString(), icon: CloudArrowDownIcon, }, ]; diff --git a/utils/api.ts b/utils/api.ts index fc86ae3..d404d34 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -11,7 +11,6 @@ export interface DashboardStats { totalConversions: number; successfulConversions: number; failedConversions: number; - totalDownloads: number; monthlyData: MonthlyData[]; walletAddresses: { eth?: string; diff --git a/utils/common.ts b/utils/common.ts new file mode 100644 index 0000000..709899d --- /dev/null +++ b/utils/common.ts @@ -0,0 +1,18 @@ +export const formatFileSize = (bytes: number): string => { + const units = ['B', 'KiB', 'MiB', 'GiB']; + let size = bytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`; +}; + +export const extractVersionFromTag = (tagName: string): string => { + // Extract version from tag like "mtm-vpn-android-v1.8.0-mtm-0.1.3" + const match = tagName.match(/v?(\d+\.\d+\.\d+(?:-mtm-\d+\.\d+\.\d+)?)/); + return match ? match[1] : tagName; +}; \ No newline at end of file diff --git a/utils/downloads.ts b/utils/downloads.ts new file mode 100644 index 0000000..0150c74 --- /dev/null +++ b/utils/downloads.ts @@ -0,0 +1,76 @@ +import { ApiError } from './api'; +import { formatFileSize, extractVersionFromTag } from './common'; + +// GitHub Releases API interfaces +export interface GitHubAsset { + name: string; + size: number; + download_count: number; + browser_download_url: string; + created_at: string; +} + +export interface GitHubAuthor { + login: string; + avatar_url: string; +} + +export interface GitHubRelease { + id: number; + tag_name: string; + name: string; + body: string; + created_at: string; + published_at: string; + author: GitHubAuthor; + assets: GitHubAsset[]; +} + +export interface ProcessedRelease { + id: string; + version: string; + downloads: number; + releaseDate: Date; + fileSize: string; + downloadUrl: string; + tagName: string; +} + +export interface DownloadStats { + totalDownloads: number; + releases: ProcessedRelease[]; +} + +export const fetchGitHubReleases = async (): Promise => { + const response = await fetch('/api/github/releases'); + if (!response.ok) { + throw new ApiError(`Failed to fetch releases: ${response.statusText}`, response.status); + } + + const releases: GitHubRelease[] = await response.json(); + + const processedReleases = releases + .filter(release => release.assets && release.assets.length > 0) + .map(release => { + // Find APK asset + const apkAsset = release.assets.find(asset => asset.name.endsWith('.apk')); + + return { + id: release.id.toString(), + version: extractVersionFromTag(release.tag_name), + downloads: apkAsset ? apkAsset.download_count : 0, + releaseDate: new Date(release.published_at || release.created_at), + fileSize: apkAsset ? formatFileSize(apkAsset.size) : 'Unknown', + downloadUrl: apkAsset ? apkAsset.browser_download_url : '#', + tagName: release.tag_name + }; + }) + .sort((a, b) => b.releaseDate.getTime() - a.releaseDate.getTime()); + + const totalDownloads = processedReleases.reduce((sum, release) => sum + release.downloads, 0); + + return { + totalDownloads, + releases: processedReleases + }; +}; \ No newline at end of file -- 2.45.2 From fd9989129abf01fe665610ffeca0424bbc9137ad Mon Sep 17 00:00:00 2001 From: Nabarun Date: Tue, 2 Sep 2025 23:20:55 +0530 Subject: [PATCH 4/5] Remove mock data and update readme --- .env.example | 6 +- README.md | 83 ++++++++++- data/mockData.ts | 264 ----------------------------------- deploy/README.md | 3 + pages/api/github/releases.ts | 2 +- types/index.ts | 55 -------- utils/api.ts | 2 +- utils/clipboard.ts | 2 +- utils/common.ts | 2 +- utils/downloads.ts | 2 +- 10 files changed, 90 insertions(+), 331 deletions(-) delete mode 100644 data/mockData.ts delete mode 100644 types/index.ts diff --git a/.env.example b/.env.example index b9cef81..d46e08f 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,8 @@ NEXT_PUBLIC_MTM_SERVICE_URL=http://localhost:3000 # NYM chain RPC endpoint NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net -# ETH chain RPC endpoint -ETH_RPC_URL= - # GitHub releases API endpoint GITHUB_RELEASES_URL=https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases + +# ETH chain RPC endpoint +ETH_RPC_URL= diff --git a/README.md b/README.md index 33cbcca..bbf102d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,82 @@ -# Test Progressive Web App +# MTM VPN Dashboard -This example used [`next-pwa`](https://github.com/shadowwalker/next-pwa) to create a progressive web app (PWA) powered by [Workbox](https://developers.google.com/web/tools/workbox/). +A Next.js dashboard application for monitoring the MTM VPN app. -## Deploy your own +## Build & Deploy -(Under construction) +See [deployment instructions](./deploy/README.md) for detailed deployment steps using the Laconic registry system. + +## Prerequisites + +- Node.js 20+ +- npm +- Access to MTM-to-NYM service API + +## Setup + +1. **Clone and install dependencies**: + ```bash + git clone https://git.vdb.to/cerc-io/mtm-vpn-dashboard.git + cd mtm-vpn-dashboard + npm install + ``` + +1. **Start mtm-to-nym-service** + + Follow steps in + +1. **Configure environment variables**: + ```bash + cp .env.example .env.local + ``` + + Update the following variables in `.env.local`: + ```env + # Public endpoint of mtm-to-nym-service + NEXT_PUBLIC_MTM_SERVICE_URL=http://localhost:3000 + + # NYM chain RPC endpoint + NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net + + # ETH chain RPC endpoint (for balance checking) + ETH_RPC_URL=https://eth.rpc.laconic.com/your-api-key + + # GitHub releases API endpoint + GITHUB_RELEASES_URL=https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases + ``` + +1. **Start the development server**: + ```bash + npm run dev -- -p 4000 + ``` + +1. **Access the application**: + Open in your browser + + +## Usage + +- Navigate to `/dashboard` for the main overview +- View transactions at `/dashboard/transactions` +- Monitor failed transactions at `/dashboard/failed` +- Check app downloads at `/dashboard/downloads` + +## API Integration + +The dashboard connects to: +- **MTM Service API**: Transaction and conversion data +- **Gitea API**: App release and download statistics (via proxy to avoid CORS) +- **Blockchain RPCs**: ETH and NYM network data for balance checking + +## Project Structure + +``` +├── components/ # Reusable UI components +├── pages/ # Next.js pages and API routes +│ ├── api/ # API proxy endpoints +│ └── dashboard/ # Dashboard pages +├── utils/ # Utility functions and API clients +├── public/ # Static assets and PWA files +├── styles/ # Global CSS and Tailwind config +└── deploy/ # Deployment configuration +``` diff --git a/data/mockData.ts b/data/mockData.ts deleted file mode 100644 index 362ef2d..0000000 --- a/data/mockData.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { Transaction, BridgeTransaction, SwapTransaction, AccountBalance, AppDownload, DashboardStats } from '../types'; - -export const mockTransactions: Transaction[] = [ - { - id: '1', - transactionHash: '2AUxZpuQqR7pYyuYcYqwbGHFgJfbvGRJfrGW1D4RSbVeHBprrCoVBb8YEb7uYAiGTL7tGLWYAbaJZjmxetDCpW9o', - fromAddress: 'HmWkGTaLQXzqDUVUThqSksp2gRYupRGKegJ4TZzBgEHi', - nymTransactionHash: '4E169C934D2782EC35DCC1BBB578FF543B081B06E76D8C030B75ACD3EDE590F8', - createdAt: new Date('2025-08-24T10:30:00Z') - }, - { - id: '2', - transactionHash: '3bVxNqrQsR8qZzrYdYrxcGHFgKgcwGTKgrHX2E5STdWfICqssEpVCc9ZFc8vZBjHTM8uHLXYBcbKam4yguEidl9P', - fromAddress: 'JkXlRgaQTbzfMcXvRuTyFqPmLdGhGfKjVnBxEzRgQaFi', - nymTransactionHash: '7F259D847F3892FD46EDD2CCC689GG654C192C17F87E9D141C86BDE4FEF701G9', - createdAt: new Date('2025-08-24T09:15:00Z') - }, - { - id: '3', - transactionHash: '4CWyOrsTt9rAarZeEasydIGGhKheaHTLhsIY3F6TUeXgJDrtfFqWDd0AhGd9wCkIUN9vIMYZCdcLbm5zhvFjelqQ', - fromAddress: 'MnYmShbRUczgOfYwSvUzGrRnHfHmGgLkWoCzF1ShRbGj', - error: 'insufficient funds: got 100unym, required 1000unym', - createdAt: new Date('2025-08-24T08:45:00Z') - }, - { - id: '4', - transactionHash: '5DXzPstUu0rBbsaZcZsycJHIhLjdxIULhsJZ4G7TUfYhJErttGqXEd1BiIe0xDlJVnOvJNZaCeCMcm6ajvHkfmrR', - fromAddress: 'NpZnTicSVd0gPdZyTyU0HsSmMeHnHhMlXpDzG2TicRiJ', - nymTransactionHash: '8G360E958G4903GE47FEE3DDD7901H765D203D28G98F0E252D97CEF5GFG812H0', - createdAt: new Date('2025-08-24T07:20:00Z') - }, - { - id: '5', - transactionHash: '6EYqRtuVv1sDbtraBdatdKIJiMkeyJVMitKa5H8UVgZiKFsuuHrYFe2CjJf1yEkKWoMaKOZbDfFNdn7bkvIlgorS', - fromAddress: 'OqAoUjdTWe1hQeaUzVV1ItTnNfIoIiNmYqEaH3UjdSjK', - error: 'account sequence mismatch, expected 42, got 41', - createdAt: new Date('2025-08-24T06:45:00Z') - }, - { - id: '6', - transactionHash: '7FZsSwvWw2tEcusbCebudLJKjNkfzKWNjuLb6I9VWhajLGtvvIsZGf3DkKg2zFlLXqZbLPZcEgGOdo8clwJmhpsT', - fromAddress: 'PrBpVkeUXf2iRfbV0WW2JuUoPgJpJjOnZrFbI4VkeTkL', - nymTransactionHash: '9H471F069H5014HF58GFF4EEE8012I876E314E39H09G1F363E08DFG6HGH023I1', - createdAt: new Date('2025-08-24T05:30:00Z') - }, - { - id: '7', - transactionHash: '8GAtTxwXx3uFdvtcDfcveMLLkOlg0LXOkvMc7J0WXibkMHuwwJtaHg4ElLh3AFmMYrAcMQAdFhHPeq9dmxKniqtU', - fromAddress: 'QsCqWlfVYg3jSgcW1XX3KvVpQhKqKkPpAsGcJ5WlfUlM', - nymTransactionHash: 'AI582G17AI6125IG69HGG5FFF9123J987F425F4AI1AH2G474F19EGH7IHI134J2', - createdAt: new Date('2025-08-24T04:15:00Z') - }, - { - id: '8', - transactionHash: '9HBuUyxYy4vGewudEgdwfNMMlPmh1MYPlwNd8K1XYjclNIvxxKubIh5FmMi4BGnNZsBdNRBeFiIQfr0eoyLofsVU', - fromAddress: 'RtDrXmgWZh4kThcX2YY4LwWqRiLrLlQrBtHdK6XmgVmN', - error: 'insufficient funds: got 50unym, required 250unym', - createdAt: new Date('2025-08-24T03:00:00Z') - }, - { - id: '9', - transactionHash: 'ACVwZzyZ5wHfxveEhdxgONNmNqmiBNaQmOe9L2YZkdmOJwyxLvcJi6GnNj5CHoOAtCeOSCfGjJRgs1fqzMqgtWV', - fromAddress: 'SuEsYnhXai5lUidY3ZZ5MxXsSkMsLmRSCuIeL7YnhWnO', - nymTransactionHash: 'BJ693H28BJ7236JH7AIIH6GGG0234K098G536G5BJ2BI3H585G20FHI8JIJ245K3', - createdAt: new Date('2025-08-24T02:45:00Z') - }, - { - id: '10', - transactionHash: 'BDCxA00a6xIgywxfFiegGOOoOrnoDObRynPf0M3aalmPKxzMwdKj7HoOk6DIpPatDeP0TCgHkKSht2grANruuXW', - fromAddress: 'TvFtZoiYbj6mVjeZ4Aa6NyYtTlNtMnStDvJfM8ZoiXoP', - error: 'tx parse error: invalid transaction format', - createdAt: new Date('2025-08-24T01:30:00Z') - }, - { - id: '11', - transactionHash: 'CEDyB11b7yJhzygGjfzgGPPpPsopEPcSzoPg1N4bmpoPLyyNxeeLk8IpPl7EJqPbuEfQ1UDhIlLTiu3hsOsVwwYX', - fromAddress: 'UwGuapjZck7nWkfa5Bb7OzZuUmOuNoTuEwKgN9apjYpQ', - nymTransactionHash: 'CK704I39CK8347KI8BJJI7HHH1345L109H647H6CK3CJ4I696H31GJJ9KJK356L4', - createdAt: new Date('2025-08-24T00:15:00Z') - }, - { - id: '12', - transactionHash: 'DFEzC22c8zKi0zhHkgAgHQQqQtqpFQdT0qQh2O5cnpqQMzzOyfFMl9JqQm8FKrQcvFgR2VEiJmMUjv4itPtWxxZY', - fromAddress: 'VxHvbqkai8oXlgb6CcC8P0avVnPvOpUvFxLhO0bqkZqR', - error: 'gas wanted 200000 exceeds block gas limit 100000', - createdAt: new Date('2025-08-23T23:00:00Z') - } -]; - -export const mockBridgeTransactions: BridgeTransaction[] = [ - { - id: 1, - nymAmount: '125.5', - ethTransactionHash: '0x1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890ab', - createdAt: new Date('2025-08-24T11:00:00Z') - }, - { - id: 2, - nymAmount: '67.25', - ethTransactionHash: '0x2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890abcd', - createdAt: new Date('2025-08-24T10:20:00Z') - }, - { - id: 3, - nymAmount: '200.0', - error: 'execution reverted: insufficient allowance', - createdAt: new Date('2025-08-24T09:30:00Z') - }, - { - id: 4, - nymAmount: '89.75', - error: 'nonce too low', - createdAt: new Date('2025-08-24T08:15:00Z') - }, - { - id: 5, - nymAmount: '156.0', - error: 'insufficient funds for gas * price + value', - createdAt: new Date('2025-08-24T07:45:00Z') - }, - { - id: 6, - nymAmount: '78.3', - ethTransactionHash: '0x3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - createdAt: new Date('2025-08-24T06:30:00Z') - }, - { - id: 7, - nymAmount: '245.8', - ethTransactionHash: '0x4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12', - createdAt: new Date('2025-08-24T05:15:00Z') - }, - { - id: 8, - nymAmount: '132.4', - error: 'execution reverted: transfer amount exceeds balance', - createdAt: new Date('2025-08-24T04:00:00Z') - }, - { - id: 9, - nymAmount: '98.7', - ethTransactionHash: '0x5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef123', - createdAt: new Date('2025-08-24T02:45:00Z') - }, - { - id: 10, - nymAmount: '187.9', - error: 'max fee per gas less than block base fee', - createdAt: new Date('2025-08-24T01:30:00Z') - }, - { - id: 11, - nymAmount: '67.5', - ethTransactionHash: '0x6f7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234', - createdAt: new Date('2025-08-23T23:15:00Z') - }, - { - id: 12, - nymAmount: '298.2', - ethTransactionHash: '0x7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456', - createdAt: new Date('2025-08-23T22:00:00Z') - } -]; - -export const mockSwapTransactions: SwapTransaction[] = [ - { - id: 1, - ethAmount: '0.05', - transactionHash: '0x3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - createdAt: new Date('2025-08-24T12:15:00Z') - }, - { - id: 2, - ethAmount: '0.025', - transactionHash: '0x4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12', - createdAt: new Date('2025-08-24T11:40:00Z') - }, - { - id: 3, - ethAmount: '0.1', - error: 'transaction underpriced: gas price too low', - createdAt: new Date('2025-08-24T10:55:00Z') - }, - { - id: 5, - ethAmount: '0.03', - error: 'replacement transaction underpriced', - createdAt: new Date('2025-08-24T08:30:00Z') - }, - { - id: 6, - ethAmount: '0.08', - transactionHash: '0x8901abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567', - createdAt: new Date('2025-08-24T07:15:00Z') - }, - { - id: 7, - ethAmount: '0.12', - transactionHash: '0x901abcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678', - createdAt: new Date('2025-08-24T06:00:00Z') - }, - { - id: 8, - ethAmount: '0.045', - error: 'execution reverted: pool insufficient liquidity', - createdAt: new Date('2025-08-24T04:45:00Z') - }, - { - id: 9, - ethAmount: '0.09', - transactionHash: '0xa01bcdef1234567890abcdef1234567890abcdef1234567890abcdef123456789', - createdAt: new Date('2025-08-24T03:30:00Z') - }, - { - id: 10, - ethAmount: '0.067', - error: 'gas limit reached', - createdAt: new Date('2025-08-24T02:15:00Z') - }, - { - id: 11, - ethAmount: '0.15', - transactionHash: '0xb01cdef1234567890abcdef1234567890abcdef1234567890abcdef123456789a', - createdAt: new Date('2025-08-24T01:00:00Z') - }, - { - id: 12, - ethAmount: '0.055', - error: 'execution reverted: deadline expired', - createdAt: new Date('2025-08-23T23:45:00Z') - } -]; - -export const mockAccountBalances: AccountBalance[] = [ - { - asset: 'ETH', - balance: '2.456789', - address: '0x742d35Cc643C0532E6A8b32F4F5bbFB6C6e13aE2' - }, - { - asset: 'NYM', - balance: '15234.789123', - address: 'n1sdnrq62m07gcwzpfmdgvqfpqqjvtnnllcypur8' - } -]; - -export const mockAppDownloads: AppDownload[] = [ - { - id: '1', - version: 'v1.8.0-mtm-0.1.2', - platform: 'Android', - downloads: 1247, - releaseDate: new Date('2025-08-19'), - fileSize: '111 MiB' - }, - { - id: '2', - version: 'v1.7.5-mtm-0.1.1', - platform: 'Android', - downloads: 892, - releaseDate: new Date('2025-08-10'), - fileSize: '108 MiB' - } -]; diff --git a/deploy/README.md b/deploy/README.md index 1b96cef..400e614 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -52,6 +52,9 @@ # NYM chain RPC endpoint NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net + # GitHub releases API endpoint + GITHUB_RELEASES_URL=https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases + # ETH chain RPC endpoint ETH_RPC_URL= ``` diff --git a/pages/api/github/releases.ts b/pages/api/github/releases.ts index c7ff106..cdf5524 100644 --- a/pages/api/github/releases.ts +++ b/pages/api/github/releases.ts @@ -32,4 +32,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) error: error instanceof Error ? error.message : 'GitHub releases request failed' }); } -} \ No newline at end of file +} diff --git a/types/index.ts b/types/index.ts deleted file mode 100644 index e534f2f..0000000 --- a/types/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -export interface Transaction { - id: string; - transactionHash: string; - fromAddress: string; - nymTransactionHash?: string; - error?: string; - createdAt: Date; -} - -export interface BridgeTransaction { - id: number; - nymAmount: string; - ethTransactionHash?: string; - error?: string; - createdAt: Date; -} - -export interface SwapTransaction { - id: number; - ethAmount: string; - transactionHash?: string; - error?: string; - createdAt: Date; -} - -export interface AccountBalance { - asset: 'ETH' | 'NYM'; - balance: string; - address: string; -} - -export interface AppDownload { - id: string; - version: string; - platform: 'Android' | 'iOS' | 'Windows' | 'macOS' | 'Linux'; - downloads: number; - releaseDate: Date; - fileSize: string; -} - - -export interface DashboardStats { - totalConversions: number; - successfulConversions: number; - failedConversions: number; - totalDownloads: number; - monthlyData: MonthlyData[]; -} - -export interface MonthlyData { - month: string; - totalConversions: number; - successfulConversions: number; - failedConversions: number; -} \ No newline at end of file diff --git a/utils/api.ts b/utils/api.ts index d404d34..6f0e3d5 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -150,4 +150,4 @@ export const dashboardApi = { }; -export default dashboardApi; \ No newline at end of file +export default dashboardApi; diff --git a/utils/clipboard.ts b/utils/clipboard.ts index faaff08..1014d89 100644 --- a/utils/clipboard.ts +++ b/utils/clipboard.ts @@ -16,4 +16,4 @@ export const copyToClipboard = async (text: string, label: string = 'Text'): Pro document.body.removeChild(textArea); console.log(`${label} copied to clipboard (fallback method)`); } -}; \ No newline at end of file +}; diff --git a/utils/common.ts b/utils/common.ts index 709899d..5418585 100644 --- a/utils/common.ts +++ b/utils/common.ts @@ -15,4 +15,4 @@ export const extractVersionFromTag = (tagName: string): string => { // Extract version from tag like "mtm-vpn-android-v1.8.0-mtm-0.1.3" const match = tagName.match(/v?(\d+\.\d+\.\d+(?:-mtm-\d+\.\d+\.\d+)?)/); return match ? match[1] : tagName; -}; \ No newline at end of file +}; diff --git a/utils/downloads.ts b/utils/downloads.ts index 0150c74..180b9a3 100644 --- a/utils/downloads.ts +++ b/utils/downloads.ts @@ -73,4 +73,4 @@ export const fetchGitHubReleases = async (): Promise => { totalDownloads, releases: processedReleases }; -}; \ No newline at end of file +}; -- 2.45.2 From 50a7e7f389eee0d95d42698ec0c82cbb405dfb62 Mon Sep 17 00:00:00 2001 From: Nabarun Date: Wed, 3 Sep 2025 00:38:58 +0530 Subject: [PATCH 5/5] Set releases URL to constant --- .env.example | 3 --- README.md | 3 --- deploy/README.md | 3 --- pages/api/github/releases.ts | 10 +++------- 4 files changed, 3 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index d46e08f..d1e1458 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,5 @@ NEXT_PUBLIC_MTM_SERVICE_URL=http://localhost:3000 # NYM chain RPC endpoint NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net -# GitHub releases API endpoint -GITHUB_RELEASES_URL=https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases - # ETH chain RPC endpoint ETH_RPC_URL= diff --git a/README.md b/README.md index bbf102d..ec79e86 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,6 @@ See [deployment instructions](./deploy/README.md) for detailed deployment steps # ETH chain RPC endpoint (for balance checking) ETH_RPC_URL=https://eth.rpc.laconic.com/your-api-key - - # GitHub releases API endpoint - GITHUB_RELEASES_URL=https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases ``` 1. **Start the development server**: diff --git a/deploy/README.md b/deploy/README.md index 400e614..1b96cef 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -52,9 +52,6 @@ # NYM chain RPC endpoint NEXT_PUBLIC_NYX_RPC_URL=https://rpc.nymtech.net - # GitHub releases API endpoint - GITHUB_RELEASES_URL=https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases - # ETH chain RPC endpoint ETH_RPC_URL= ``` diff --git a/pages/api/github/releases.ts b/pages/api/github/releases.ts index cdf5524..4a9b5fc 100644 --- a/pages/api/github/releases.ts +++ b/pages/api/github/releases.ts @@ -1,18 +1,14 @@ import { NextApiRequest, NextApiResponse } from 'next'; +const GITHUB_RELEASES_URL="https://git.vdb.to/api/v1/repos/cerc-io/mtm-vpn-client-public/releases" + export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'GET') { return res.status(405).json({ error: 'Method not allowed' }); } - const githubReleasesUrl = process.env.GITHUB_RELEASES_URL; - - if (!githubReleasesUrl) { - return res.status(500).json({ error: 'GitHub releases URL not configured' }); - } - try { - const response = await fetch(githubReleasesUrl, { + const response = await fetch(GITHUB_RELEASES_URL, { method: 'GET', headers: { 'Accept': 'application/json', -- 2.45.2