From 91c115c4655a5c03d55f31b4a767202537010f83 Mon Sep 17 00:00:00 2001 From: lissavxo Date: Mon, 14 Apr 2025 14:25:41 -0300 Subject: [PATCH 1/6] feat: add fetchTransactionsByPaybuttonIdWithPagination --- pages/api/paybutton/transactions/[id].ts | 17 +++++- services/transactionService.ts | 73 ++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/pages/api/paybutton/transactions/[id].ts b/pages/api/paybutton/transactions/[id].ts index 3a7dbd046..10c62ff6b 100644 --- a/pages/api/paybutton/transactions/[id].ts +++ b/pages/api/paybutton/transactions/[id].ts @@ -1,5 +1,5 @@ -import { RESPONSE_MESSAGES } from 'constants/index' -import { fetchTransactionsByPaybuttonId } from 'services/transactionService' +import { RESPONSE_MESSAGES, TX_PAGE_SIZE_LIMIT } from 'constants/index' +import { fetchTransactionsByPaybuttonIdWithPagination } from 'services/transactionService' import * as paybuttonService from 'services/paybuttonService' import { setSession } from 'utils/setSession' import { parseError } from 'utils/validators' @@ -9,6 +9,17 @@ export default async (req: any, res: any): Promise => { await setSession(req, res) const userId = req.session.userId const paybuttonId = req.query.id as string + const page = (req.query.page === '' || req.query.page === undefined) ? 0 : Number(req.query.page) + const pageSize = (req.query.pageSize === '' || req.query.pageSize === undefined) ? DEFAULT_TX_PAGE_SIZE : Number(req.query.pageSize) + const orderBy = (req.query.orderBy === '' || req.query.orderBy === undefined) ? undefined : req.query.orderBy as string + const orderDesc: boolean = !!(req.query.orderDesc === '' || req.query.orderDesc === undefined || req.query.orderDesc === 'true') + + if (isNaN(page) || isNaN(pageSize)) { + throw new Error(RESPONSE_MESSAGES.PAGE_SIZE_AND_PAGE_SHOULD_BE_NUMBERS_400.message) + } + if (pageSize > TX_PAGE_SIZE_LIMIT) { + throw new Error(RESPONSE_MESSAGES.PAGE_SIZE_LIMIT_EXCEEDED_400.message) + } try { const paybutton = await paybuttonService.fetchPaybuttonById(paybuttonId) @@ -16,7 +27,7 @@ export default async (req: any, res: any): Promise => { throw new Error(RESPONSE_MESSAGES.RESOURCE_DOES_NOT_BELONG_TO_USER_400.message) } - const transactions = await fetchTransactionsByPaybuttonId(paybuttonId) + const transactions = await fetchTransactionsByPaybuttonIdWithPagination(paybuttonId, page, pageSize, orderDesc, orderBy) res.status(200).json({ transactions }) } catch (err: any) { diff --git a/services/transactionService.ts b/services/transactionService.ts index 4fa9a8d2a..260c1a207 100644 --- a/services/transactionService.ts +++ b/services/transactionService.ts @@ -144,6 +144,56 @@ export async function fetchTransactionsByAddressList ( }) } +export async function fetchTransactionsByAddressListWithPagination ( + addressIdList: string[], + page: number, + pageSize: number, + orderBy?: string, + orderDesc = true, + networkIdsListFilter?: number[], +): Promise { + + const orderDescString: Prisma.SortOrder = orderDesc ? 'desc' : 'asc' + + let orderByQuery + if (orderBy !== undefined && orderBy !== '') { + if (orderBy.includes('.')) { + const [relation, property] = orderBy.split('.') + orderByQuery = { + [relation]: { + [property]: orderDescString + } + } + } else { + orderByQuery = { + [orderBy]: orderDescString + } + } + } else { + // Default orderBy + orderByQuery = { + timestamp: orderDescString + } + } + + return await prisma.transaction.findMany({ + where: { + addressId: { + in: addressIdList + }, + address: { + networkId: { + in: networkIdsListFilter ?? Object.values(NETWORK_IDS) + } + } + }, + include: includePaybuttonsAndPrices, + orderBy: orderByQuery, + skip: page * pageSize, + take: pageSize, + }) +} + export async function fetchTxCountByAddressString (addressString: string): Promise { return await prisma.transaction.count({ where: { @@ -512,6 +562,29 @@ export async function fetchTransactionsByPaybuttonId (paybuttonId: string, netwo return transactions } +export async function fetchTransactionsByPaybuttonIdWithPagination ( + paybuttonId: string, + page: number, + pageSize: number, + orderDesc: boolean, + orderBy?: string, + networkIds?: number[]): Promise { + const addressIdList = await fetchAddressesByPaybuttonId(paybuttonId) + const transactions = await fetchTransactionsByAddressListWithPagination( + addressIdList, + page, + pageSize, + orderBy, + orderDesc, + networkIds); + + if (transactions.length === 0) { + throw new Error(RESPONSE_MESSAGES.NO_TRANSACTION_FOUND_404.message) + } + + return transactions +} + export const getTransactionValueInCurrency = (transaction: TransactionWithAddressAndPrices, currency: SupportedQuotesType): number => { const { prices, From 98088ad371a7ae588589382e02a3e4781a2e5217 Mon Sep 17 00:00:00 2001 From: lissavxo Date: Mon, 14 Apr 2025 14:26:14 -0300 Subject: [PATCH 2/6] feat: merge addresses --- .../TableContainer/TableContainerGetter.tsx | 32 +++++++------- .../Transaction/AddressTransactions.tsx | 44 ++++++++++--------- pages/button/[id].tsx | 4 +- styles/global.css | 14 ++++++ 4 files changed, 56 insertions(+), 38 deletions(-) diff --git a/components/TableContainer/TableContainerGetter.tsx b/components/TableContainer/TableContainerGetter.tsx index 5eb2bd0b7..ea583cab0 100644 --- a/components/TableContainer/TableContainerGetter.tsx +++ b/components/TableContainer/TableContainerGetter.tsx @@ -35,9 +35,11 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp const [pageCount, setPageCount] = useState(0) const [loading, setLoading] = useState(true) const emptyMessageDisplay = emptyMessage ?? DEFAULT_EMPTY_TABLE_MESSAGE + const [hiddenColumns, setHiddenColumns] = useState({}) const triggerSort = (column: any): void => { - if (column.disableSortBy === true) return + if (column.disableSortBy === true || hiddenColumns[column.id]) return + const id = column.id if (sortColumn === id) { setSortDesc(!sortDesc) @@ -47,6 +49,10 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp } gotoPage(0) } + + const toggleColumn = (id: any): void => { + setHiddenColumns((prev) => ({ ...prev, [id]: !prev[id]})) + } const { getTableProps, @@ -84,16 +90,6 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp })() }, [pageSize, pageIndex, sortColumn, sortDesc, tableRefreshCount]) - const generateSortingIndicator = (column: any): JSX.Element | null => { - if (sortColumn === column.id) { - if (sortDesc) { - return
- } else { - return
- } - } - return null - } const onChangeInSelect = (event: any): void => { const pageSize = Number(event.target.value) @@ -110,8 +106,14 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp {headerGroup.headers.map((column: any) => ( { triggerSort(column) }}> +
{column.render('Header')} - {generateSortingIndicator(column)} + {column.shrinkable && ( + toggleColumn(column.id)} style={{ marginRight: 8, cursor: 'pointer' }}> + {hiddenColumns[column.id] ?
:
} + + )} +
))} @@ -125,9 +127,9 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp prepareRow(row) return ( - {row.cells.map((cell: any) => { - return {cell.render('Cell')} - })} + {row.cells.map((cell: any) => + hiddenColumns[cell.column.id] ? null : {cell.render('Cell')} + )} ) }) diff --git a/components/Transaction/AddressTransactions.tsx b/components/Transaction/AddressTransactions.tsx index 6f169fc06..a163f7eff 100644 --- a/components/Transaction/AddressTransactions.tsx +++ b/components/Transaction/AddressTransactions.tsx @@ -1,5 +1,4 @@ import React, { useMemo } from 'react' -import style from './transaction.module.css' import Image from 'next/image' import XECIcon from 'assets/xec-logo.png' import BCHIcon from 'assets/bch-logo.png' @@ -15,26 +14,29 @@ interface IProps { addressSyncing: { [address: string]: boolean } + paybuttonId: string tableRefreshCount: number timezone: string } -function getGetterForAddress (addressString: string): Function { +function fetchTransactionsByPaybuttonId (paybuttonId: string): Function { return async (page: number, pageSize: number, orderBy: string, orderDesc: boolean) => { - const ok = await fetch(`/api/address/transactions/${addressString}?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&orderDesc=${String(orderDesc)}`, { + const response = await fetch(`/api/paybutton/transactions/${paybuttonId}?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&orderDesc=${String(orderDesc)}`, { headers: { Timezone: moment.tz.guess() } }) - const ok2 = await fetch(`/api/address/transactions/count/${addressString}`) + const responseCount = await fetch(`/api/paybutton/transactions/count/${paybuttonId}`) + const transactions = await response.json(); + const count = await responseCount.json(); return { - data: await ok.json(), - totalCount: await ok2.json() + data: transactions.transactions, + totalCount: count } } } -export default ({ addressSyncing, tableRefreshCount, timezone = moment.tz.guess() }: IProps): JSX.Element => { +export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = moment.tz.guess() }: IProps): JSX.Element => { const columns = useMemo( () => [ { @@ -93,25 +95,25 @@ export default ({ addressSyncing, tableRefreshCount, timezone = moment.tz.guess( ) } - } + }, + { + Header: () => (
Address
), + accessor: 'address.address', + shrinkable: true, + Cell: (cellProps) => { + return ( +
+ {cellProps.cell.value} +
+ ) + } + }, ], [] ) return ( <> - {Object.keys(addressSyncing).map(transactionAddress => ( -
-
-
{transactionAddress}
- -
- View on explorer -
-
-
- -
- ))} + ) } diff --git a/pages/button/[id].tsx b/pages/button/[id].tsx index 0bb50cbc2..b4e6905f7 100644 --- a/pages/button/[id].tsx +++ b/pages/button/[id].tsx @@ -101,7 +101,7 @@ export default function PayButton (props: PaybuttonProps): React.ReactElement { }) socket.on(SOCKET_MESSAGES.INCOMING_TXS, (broadcastedData: BroadcastTxData) => { - setTableRefreshCount(tableRefreshCount + 1) + setTableRefreshCount(tableRefreshCountCurrent => tableRefreshCountCurrent + 1) updateIsSyncing([broadcastedData.address]) }) } @@ -207,7 +207,7 @@ export default function PayButton (props: PaybuttonProps): React.ReactElement {
- + ) diff --git a/styles/global.css b/styles/global.css index 8bd3dae12..96b2bf76a 100644 --- a/styles/global.css +++ b/styles/global.css @@ -161,6 +161,20 @@ button:enabled:hover { background: #f4f4f4; } +.table-arrow-right { + width: 0; + height: 0; + border-style: solid; + border-width: 4px 5px 4px 0; + border-color: transparent var(--primary-text-color) transparent transparent; + position: absolute; + right: 0; + top: 0; + bottom: 0; + margin: auto; + opacity: 0.5; +} + .table-sort-arrow-down, .table-sort-arrow-up { width: 0; From 0f40293cf4db0b80f7fd2ce138ae1611863fb4ed Mon Sep 17 00:00:00 2001 From: lissavxo Date: Thu, 24 Apr 2025 12:20:08 -0300 Subject: [PATCH 3/6] fix: button detail table --- .../TableContainer/TableContainerGetter.tsx | 8 ++++---- components/Transaction/AddressTransactions.tsx | 16 +++++++++------- services/transactionService.ts | 1 + styles/global.css | 5 +++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/components/TableContainer/TableContainerGetter.tsx b/components/TableContainer/TableContainerGetter.tsx index ea583cab0..96c103007 100644 --- a/components/TableContainer/TableContainerGetter.tsx +++ b/components/TableContainer/TableContainerGetter.tsx @@ -106,11 +106,11 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp {headerGroup.headers.map((column: any) => ( { triggerSort(column) }}> -
+
{column.render('Header')} {column.shrinkable && ( - toggleColumn(column.id)} style={{ marginRight: 8, cursor: 'pointer' }}> - {hiddenColumns[column.id] ?
:
} + toggleColumn(column.id)} style={{ cursor: 'pointer' }}> + {hiddenColumns[column.id] ?
:
} )}
@@ -128,7 +128,7 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp return ( {row.cells.map((cell: any) => - hiddenColumns[cell.column.id] ? null : {cell.render('Cell')} + hiddenColumns[cell.column.id] ? : {cell.render('Cell')} )} ) diff --git a/components/Transaction/AddressTransactions.tsx b/components/Transaction/AddressTransactions.tsx index a163f7eff..6961bec89 100644 --- a/components/Transaction/AddressTransactions.tsx +++ b/components/Transaction/AddressTransactions.tsx @@ -82,22 +82,24 @@ export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = mom } }, { - Header: 'TX', + Header: () => (
TX
), accessor: 'hash', disableSortBy: true, Cell: (cellProps) => { const url = cellProps.cell.row.values['address.networkId'] === 1 ? XEC_TX_EXPLORER_URL : BCH_TX_EXPLORER_URL return ( - -
- View on explorer -
-
+ ) } }, { - Header: () => (
Address
), + Header: () => (
Address
), accessor: 'address.address', shrinkable: true, Cell: (cellProps) => { diff --git a/services/transactionService.ts b/services/transactionService.ts index 260c1a207..a060630a2 100644 --- a/services/transactionService.ts +++ b/services/transactionService.ts @@ -762,6 +762,7 @@ export async function fetchAllPaymentsByUserIdWithPagination ( userId, page, pageSize, orderDesc ) } + // Get query for orderBy that works with nested properties (e.g. `address.networkId`) let orderByQuery if (orderBy !== undefined && orderBy !== '') { if (orderBy === 'values') { diff --git a/styles/global.css b/styles/global.css index 96b2bf76a..4ed7ce91e 100644 --- a/styles/global.css +++ b/styles/global.css @@ -233,12 +233,13 @@ button:enabled:hover { } .table-eye-ctn { - text-align: right; + text-align: center; display: flex; align-items: center; - justify-content: flex-end; + justify-content: center; } + .table-eye { width: 20px; opacity: 0.6; From c57dfba820c7870ba18f436adab23a0529f3b8e3 Mon Sep 17 00:00:00 2001 From: lissavxo Date: Mon, 5 May 2025 12:13:16 -0300 Subject: [PATCH 4/6] chore: add comment on fetchTransactionsByAddressListWithPagination --- services/transactionService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/transactionService.ts b/services/transactionService.ts index a060630a2..c3f80dd0b 100644 --- a/services/transactionService.ts +++ b/services/transactionService.ts @@ -154,7 +154,8 @@ export async function fetchTransactionsByAddressListWithPagination ( ): Promise { const orderDescString: Prisma.SortOrder = orderDesc ? 'desc' : 'asc' - + + // Get query for orderBy that works with nested properties (e.g. `address.networkId`) let orderByQuery if (orderBy !== undefined && orderBy !== '') { if (orderBy.includes('.')) { From 1d6e812f3a5b9c3c02afe64dd6922279687bd25d Mon Sep 17 00:00:00 2001 From: lissavxo Date: Mon, 5 May 2025 17:15:09 -0300 Subject: [PATCH 5/6] fix: sorting arrow --- components/TableContainer/TableContainerGetter.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/TableContainer/TableContainerGetter.tsx b/components/TableContainer/TableContainerGetter.tsx index 96c103007..8061e1b39 100644 --- a/components/TableContainer/TableContainerGetter.tsx +++ b/components/TableContainer/TableContainerGetter.tsx @@ -90,6 +90,16 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp })() }, [pageSize, pageIndex, sortColumn, sortDesc, tableRefreshCount]) + const generateSortingIndicator = (column: any): JSX.Element | null => { + if (sortColumn === column.id) { + if (sortDesc) { + return
+ } else { + return
+ } + } + return null + } const onChangeInSelect = (event: any): void => { const pageSize = Number(event.target.value) @@ -113,6 +123,7 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp {hiddenColumns[column.id] ?
:
} )} + {!column.shrinkable && generateSortingIndicator(column)}
))} From df1856b559598df5320471a0e86c855aa5df4fc1 Mon Sep 17 00:00:00 2001 From: lissavxo Date: Fri, 9 May 2025 16:41:30 -0300 Subject: [PATCH 6/6] refactor: rename AddressTransactions --- ...essTransactions.tsx => PaybuttonTransactions.tsx} | 12 ++++++------ components/Transaction/index.tsx | 4 ++-- pages/button/[id].tsx | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) rename components/Transaction/{AddressTransactions.tsx => PaybuttonTransactions.tsx} (93%) diff --git a/components/Transaction/AddressTransactions.tsx b/components/Transaction/PaybuttonTransactions.tsx similarity index 93% rename from components/Transaction/AddressTransactions.tsx rename to components/Transaction/PaybuttonTransactions.tsx index 6961bec89..db6fa90b5 100644 --- a/components/Transaction/AddressTransactions.tsx +++ b/components/Transaction/PaybuttonTransactions.tsx @@ -5,7 +5,7 @@ import BCHIcon from 'assets/bch-logo.png' import EyeIcon from 'assets/eye-icon.png' import CheckIcon from 'assets/check-icon.png' import XIcon from 'assets/x-icon.png' -import TableContainerGetter from '../../components/TableContainer/TableContainerGetter' +import TableContainerGetter from '../TableContainer/TableContainerGetter' import { compareNumericString } from 'utils/index' import moment from 'moment-timezone' import { XEC_TX_EXPLORER_URL, BCH_TX_EXPLORER_URL } from 'constants/index' @@ -27,8 +27,8 @@ function fetchTransactionsByPaybuttonId (paybuttonId: string): Function { } }) const responseCount = await fetch(`/api/paybutton/transactions/count/${paybuttonId}`) - const transactions = await response.json(); - const count = await responseCount.json(); + const transactions = await response.json() + const count = await responseCount.json() return { data: transactions.transactions, totalCount: count @@ -94,12 +94,12 @@ export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = mom View on explorer
-
+
) } }, { - Header: () => (
Address
), + Header: () => (
Address
), accessor: 'address.address', shrinkable: true, Cell: (cellProps) => { @@ -109,7 +109,7 @@ export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = mom
) } - }, + } ], [] ) diff --git a/components/Transaction/index.tsx b/components/Transaction/index.tsx index 2cab8a8e0..193bb5bec 100644 --- a/components/Transaction/index.tsx +++ b/components/Transaction/index.tsx @@ -1,5 +1,5 @@ -import AddressTransactions from './AddressTransactions' +import PaybuttonTransactions from './PaybuttonTransactions' export { - AddressTransactions + PaybuttonTransactions } diff --git a/pages/button/[id].tsx b/pages/button/[id].tsx index b4e6905f7..b1729ac41 100644 --- a/pages/button/[id].tsx +++ b/pages/button/[id].tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react' import Page from 'components/Page' import { PaybuttonDetail } from 'components/Paybutton' import { PaybuttonWithAddresses } from 'services/paybuttonService' -import { AddressTransactions } from 'components/Transaction' +import { PaybuttonTransactions } from 'components/Transaction' import supertokensNode from 'supertokens-node' import * as SuperTokensConfig from '../../config/backendConfig' import Session from 'supertokens-node/recipe/session' @@ -207,7 +207,7 @@ export default function PayButton (props: PaybuttonProps): React.ReactElement {
- + )