Skip to content

Commit 755a7a4

Browse files
authored
Merge pull request #998 from PayButton/feat/merge-addresses-button-detail
Feat/merge addresses button detail
2 parents 06e5e3a + fc701fa commit 755a7a4

7 files changed

Lines changed: 160 additions & 43 deletions

File tree

components/TableContainer/TableContainerGetter.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp
3535
const [pageCount, setPageCount] = useState(0)
3636
const [loading, setLoading] = useState(true)
3737
const emptyMessageDisplay = emptyMessage ?? DEFAULT_EMPTY_TABLE_MESSAGE
38+
const [hiddenColumns, setHiddenColumns] = useState({})
3839

3940
const triggerSort = (column: any): void => {
40-
if (column.disableSortBy === true) return
41+
if (column.disableSortBy === true || hiddenColumns[column.id]) return
42+
4143
const id = column.id
4244
if (sortColumn === id) {
4345
setSortDesc(!sortDesc)
@@ -47,6 +49,10 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp
4749
}
4850
gotoPage(0)
4951
}
52+
53+
const toggleColumn = (id: any): void => {
54+
setHiddenColumns((prev) => ({ ...prev, [id]: !prev[id]}))
55+
}
5056

5157
const {
5258
getTableProps,
@@ -114,8 +120,15 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp
114120
<tr {...headerGroup.getHeaderGroupProps()}>
115121
{headerGroup.headers.map((column: any) => (
116122
<th {...column.getHeaderProps()} style={column.disableSortBy === true ? null : { cursor: 'pointer' }} onClick={() => { triggerSort(column) }}>
123+
<div>
117124
{column.render('Header')}
118-
{generateSortingIndicator(column)}
125+
{column.shrinkable && (
126+
<span onClick={() => toggleColumn(column.id)} style={{ cursor: 'pointer' }}>
127+
{hiddenColumns[column.id] ? <div style= {{marginRight: '5px'}} className='table-arrow-right' /> : <div style= {{marginRight: '5px'}} className='table-sort-arrow-down' />}
128+
</span>
129+
)}
130+
{!column.shrinkable && generateSortingIndicator(column)}
131+
</div>
119132
</th>
120133
))}
121134
</tr>
@@ -129,9 +142,9 @@ const TableContainer = ({ columns, dataGetter, opts, ssr, tableRefreshCount, emp
129142
prepareRow(row)
130143
return (
131144
<tr {...row.getRowProps()}>
132-
{row.cells.map((cell: any) => {
133-
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
134-
})}
145+
{row.cells.map((cell: any) =>
146+
hiddenColumns[cell.column.id] ? <td> </td> : <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
147+
)}
135148
</tr>
136149
)
137150
})

components/Transaction/AddressTransactions.tsx renamed to components/Transaction/PaybuttonTransactions.tsx

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React, { useMemo } from 'react'
2-
import style from './transaction.module.css'
32
import Image from 'next/image'
43
import XECIcon from 'assets/xec-logo.png'
54
import BCHIcon from 'assets/bch-logo.png'
65
import EyeIcon from 'assets/eye-icon.png'
76
import CheckIcon from 'assets/check-icon.png'
87
import XIcon from 'assets/x-icon.png'
9-
import TableContainerGetter from '../../components/TableContainer/TableContainerGetter'
8+
import TableContainerGetter from '../TableContainer/TableContainerGetter'
109
import { compareNumericString } from 'utils/index'
1110
import moment from 'moment-timezone'
1211
import { XEC_TX_EXPLORER_URL, BCH_TX_EXPLORER_URL } from 'constants/index'
@@ -15,26 +14,29 @@ interface IProps {
1514
addressSyncing: {
1615
[address: string]: boolean
1716
}
17+
paybuttonId: string
1818
tableRefreshCount: number
1919
timezone: string
2020
}
2121

22-
function getGetterForAddress (addressString: string): Function {
22+
function fetchTransactionsByPaybuttonId (paybuttonId: string): Function {
2323
return async (page: number, pageSize: number, orderBy: string, orderDesc: boolean) => {
24-
const ok = await fetch(`/api/address/transactions/${addressString}?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&orderDesc=${String(orderDesc)}`, {
24+
const response = await fetch(`/api/paybutton/transactions/${paybuttonId}?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&orderDesc=${String(orderDesc)}`, {
2525
headers: {
2626
Timezone: moment.tz.guess()
2727
}
2828
})
29-
const ok2 = await fetch(`/api/address/transactions/count/${addressString}`)
29+
const responseCount = await fetch(`/api/paybutton/transactions/count/${paybuttonId}`)
30+
const transactions = await response.json()
31+
const count = await responseCount.json()
3032
return {
31-
data: await ok.json(),
32-
totalCount: await ok2.json()
33+
data: transactions.transactions,
34+
totalCount: count
3335
}
3436
}
3537
}
3638

37-
export default ({ addressSyncing, tableRefreshCount, timezone = moment.tz.guess() }: IProps): JSX.Element => {
39+
export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = moment.tz.guess() }: IProps): JSX.Element => {
3840
const columns = useMemo(
3941
() => [
4042
{
@@ -80,17 +82,31 @@ export default ({ addressSyncing, tableRefreshCount, timezone = moment.tz.guess(
8082
}
8183
},
8284
{
83-
Header: 'TX',
85+
Header: () => (<div style={{ textAlign: 'center' }}>TX</div>),
8486
accessor: 'hash',
8587
disableSortBy: true,
8688
Cell: (cellProps) => {
8789
const url = cellProps.cell.row.values['address.networkId'] === 1 ? XEC_TX_EXPLORER_URL : BCH_TX_EXPLORER_URL
8890
return (
89-
<a href={url.concat(cellProps.cell.value)} target="_blank" rel="noopener noreferrer" className="table-eye-ctn">
90-
<div className="table-eye">
91-
<Image src={EyeIcon} alt='View on explorer' />
92-
</div>
93-
</a>
91+
<div className="table-eye-ctn">
92+
<a href={url.concat(cellProps.cell.value)} target="_blank" rel="noopener noreferrer">
93+
<div className="table-eye">
94+
<Image src={EyeIcon} alt='View on explorer' />
95+
</div>
96+
</a>
97+
</div>
98+
)
99+
}
100+
},
101+
{
102+
Header: () => (<div style={{ marginRight: '1px' }}>Address</div>),
103+
accessor: 'address.address',
104+
shrinkable: true,
105+
Cell: (cellProps) => {
106+
return (
107+
<div>
108+
{cellProps.cell.value}
109+
</div>
94110
)
95111
}
96112
}
@@ -99,19 +115,7 @@ export default ({ addressSyncing, tableRefreshCount, timezone = moment.tz.guess(
99115
)
100116
return (
101117
<>
102-
{Object.keys(addressSyncing).map(transactionAddress => (
103-
<div key={transactionAddress} className='address-transactions-ctn'>
104-
<div className={style.tablelabel}>
105-
<div>{transactionAddress}</div>
106-
<a href={transactionAddress.slice(0, 5) === 'ecash' ? `https://explorer.e.cash/address/${transactionAddress}` : `https://blockchair.com/bitcoin-cash/address/${transactionAddress}`} target="_blank" rel="noopener noreferrer" className="table-eye-ctn">
107-
<div className="table-eye">
108-
<Image src={EyeIcon} alt='View on explorer' />
109-
</div>
110-
</a>
111-
</div>
112-
<TableContainerGetter columns={columns} dataGetter={getGetterForAddress(transactionAddress)} tableRefreshCount={tableRefreshCount} emptyMessage={'No transactions.'}/>
113-
</div>
114-
))}
118+
<TableContainerGetter columns={columns} dataGetter={fetchTransactionsByPaybuttonId(paybuttonId)} tableRefreshCount={tableRefreshCount} emptyMessage={'No transactions.'}/>
115119
</>
116120
)
117121
}

components/Transaction/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import AddressTransactions from './AddressTransactions'
1+
import PaybuttonTransactions from './PaybuttonTransactions'
22

33
export {
4-
AddressTransactions
4+
PaybuttonTransactions
55
}

pages/api/paybutton/transactions/[id].ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { RESPONSE_MESSAGES } from 'constants/index'
2-
import { fetchTransactionsByPaybuttonId } from 'services/transactionService'
1+
import { RESPONSE_MESSAGES, TX_PAGE_SIZE_LIMIT } from 'constants/index'
2+
import { fetchTransactionsByPaybuttonIdWithPagination } from 'services/transactionService'
33
import * as paybuttonService from 'services/paybuttonService'
44
import { setSession } from 'utils/setSession'
55
import { parseError } from 'utils/validators'
@@ -9,14 +9,25 @@ export default async (req: any, res: any): Promise<void> => {
99
await setSession(req, res)
1010
const userId = req.session.userId
1111
const paybuttonId = req.query.id as string
12+
const page = (req.query.page === '' || req.query.page === undefined) ? 0 : Number(req.query.page)
13+
const pageSize = (req.query.pageSize === '' || req.query.pageSize === undefined) ? DEFAULT_TX_PAGE_SIZE : Number(req.query.pageSize)
14+
const orderBy = (req.query.orderBy === '' || req.query.orderBy === undefined) ? undefined : req.query.orderBy as string
15+
const orderDesc: boolean = !!(req.query.orderDesc === '' || req.query.orderDesc === undefined || req.query.orderDesc === 'true')
16+
17+
if (isNaN(page) || isNaN(pageSize)) {
18+
throw new Error(RESPONSE_MESSAGES.PAGE_SIZE_AND_PAGE_SHOULD_BE_NUMBERS_400.message)
19+
}
20+
if (pageSize > TX_PAGE_SIZE_LIMIT) {
21+
throw new Error(RESPONSE_MESSAGES.PAGE_SIZE_LIMIT_EXCEEDED_400.message)
22+
}
1223

1324
try {
1425
const paybutton = await paybuttonService.fetchPaybuttonById(paybuttonId)
1526
if (paybutton.providerUserId !== userId) {
1627
throw new Error(RESPONSE_MESSAGES.RESOURCE_DOES_NOT_BELONG_TO_USER_400.message)
1728
}
1829

19-
const transactions = await fetchTransactionsByPaybuttonId(paybuttonId)
30+
const transactions = await fetchTransactionsByPaybuttonIdWithPagination(paybuttonId, page, pageSize, orderDesc, orderBy)
2031

2132
res.status(200).json({ transactions })
2233
} catch (err: any) {

pages/button/[id].tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
22
import Page from 'components/Page'
33
import { PaybuttonDetail } from 'components/Paybutton'
44
import { PaybuttonWithAddresses } from 'services/paybuttonService'
5-
import { AddressTransactions } from 'components/Transaction'
5+
import { PaybuttonTransactions } from 'components/Transaction'
66
import supertokensNode from 'supertokens-node'
77
import * as SuperTokensConfig from '../../config/backendConfig'
88
import Session from 'supertokens-node/recipe/session'
@@ -101,7 +101,7 @@ export default function PayButton (props: PaybuttonProps): React.ReactElement {
101101
})
102102

103103
socket.on(SOCKET_MESSAGES.INCOMING_TXS, (broadcastedData: BroadcastTxData) => {
104-
setTableRefreshCount(tableRefreshCount + 1)
104+
setTableRefreshCount(tableRefreshCountCurrent => tableRefreshCountCurrent + 1)
105105
updateIsSyncing([broadcastedData.address])
106106
})
107107
}
@@ -207,7 +207,7 @@ export default function PayButton (props: PaybuttonProps): React.ReactElement {
207207
</div>
208208
</div>
209209

210-
<AddressTransactions addressSyncing={isSyncing} tableRefreshCount={tableRefreshCount} timezone={timezone}/>
210+
<PaybuttonTransactions addressSyncing={isSyncing} paybuttonId={paybutton.id} tableRefreshCount={tableRefreshCount} timezone={timezone}/>
211211
<PaybuttonTrigger emailCredits={userProfile.emailCredits} paybuttonId={paybutton.id}/>
212212
</>
213213
)

services/transactionService.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,57 @@ export async function fetchTransactionsByAddressList (
145145
})
146146
}
147147

148+
export async function fetchTransactionsByAddressListWithPagination (
149+
addressIdList: string[],
150+
page: number,
151+
pageSize: number,
152+
orderBy?: string,
153+
orderDesc = true,
154+
networkIdsListFilter?: number[],
155+
): Promise<TransactionsWithPaybuttonsAndPrices[]> {
156+
157+
const orderDescString: Prisma.SortOrder = orderDesc ? 'desc' : 'asc'
158+
159+
// Get query for orderBy that works with nested properties (e.g. `address.networkId`)
160+
let orderByQuery
161+
if (orderBy !== undefined && orderBy !== '') {
162+
if (orderBy.includes('.')) {
163+
const [relation, property] = orderBy.split('.')
164+
orderByQuery = {
165+
[relation]: {
166+
[property]: orderDescString
167+
}
168+
}
169+
} else {
170+
orderByQuery = {
171+
[orderBy]: orderDescString
172+
}
173+
}
174+
} else {
175+
// Default orderBy
176+
orderByQuery = {
177+
timestamp: orderDescString
178+
}
179+
}
180+
181+
return await prisma.transaction.findMany({
182+
where: {
183+
addressId: {
184+
in: addressIdList
185+
},
186+
address: {
187+
networkId: {
188+
in: networkIdsListFilter ?? Object.values(NETWORK_IDS)
189+
}
190+
}
191+
},
192+
include: includePaybuttonsAndPrices,
193+
orderBy: orderByQuery,
194+
skip: page * pageSize,
195+
take: pageSize,
196+
})
197+
}
198+
148199
export async function fetchTxCountByAddressString (addressString: string): Promise<number> {
149200
return await prisma.transaction.count({
150201
where: {
@@ -513,6 +564,29 @@ export async function fetchTransactionsByPaybuttonId (paybuttonId: string, netwo
513564
return transactions
514565
}
515566

567+
export async function fetchTransactionsByPaybuttonIdWithPagination (
568+
paybuttonId: string,
569+
page: number,
570+
pageSize: number,
571+
orderDesc: boolean,
572+
orderBy?: string,
573+
networkIds?: number[]): Promise<TransactionsWithPaybuttonsAndPrices[]> {
574+
const addressIdList = await fetchAddressesByPaybuttonId(paybuttonId)
575+
const transactions = await fetchTransactionsByAddressListWithPagination(
576+
addressIdList,
577+
page,
578+
pageSize,
579+
orderBy,
580+
orderDesc,
581+
networkIds);
582+
583+
if (transactions.length === 0) {
584+
throw new Error(RESPONSE_MESSAGES.NO_TRANSACTION_FOUND_404.message)
585+
}
586+
587+
return transactions
588+
}
589+
516590
export const getTransactionValueInCurrency = (transaction: TransactionWithAddressAndPrices, currency: SupportedQuotesType): number => {
517591
const {
518592
prices,
@@ -661,7 +735,7 @@ export async function fetchAllPaymentsByUserIdWithPagination (
661735
userId, page, pageSize, orderDesc, buttonIds
662736
)
663737
}
664-
738+
// Get query for orderBy that works with nested properties (e.g. `address.networkId`)
665739
let orderByQuery
666740
if (orderBy !== undefined && orderBy !== '') {
667741
if (orderBy === 'values') {

styles/global.css

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,20 @@ button:enabled:hover {
161161
background: #f4f4f4;
162162
}
163163

164+
.table-arrow-right {
165+
width: 0;
166+
height: 0;
167+
border-style: solid;
168+
border-width: 4px 5px 4px 0;
169+
border-color: transparent var(--primary-text-color) transparent transparent;
170+
position: absolute;
171+
right: 0;
172+
top: 0;
173+
bottom: 0;
174+
margin: auto;
175+
opacity: 0.5;
176+
}
177+
164178
.table-sort-arrow-down,
165179
.table-sort-arrow-up {
166180
width: 0;
@@ -219,12 +233,13 @@ button:enabled:hover {
219233
}
220234

221235
.table-eye-ctn {
222-
text-align: right;
236+
text-align: center;
223237
display: flex;
224238
align-items: center;
225-
justify-content: flex-end;
239+
justify-content: center;
226240
}
227241

242+
228243
.table-eye {
229244
width: 20px;
230245
opacity: 0.6;

0 commit comments

Comments
 (0)