paginates transactions

This commit is contained in:
jessop 2019-09-22 22:47:07 -04:00 committed by Sean Yesmunt
parent 534d84ef88
commit 5078bd6699
8 changed files with 217 additions and 140 deletions

View file

@ -17,6 +17,7 @@ type Props = {
defaultPath?: string,
filters: Array<string>,
onFileCreated?: string => void,
disabled: boolean,
};
class FileExporter extends React.PureComponent<Props> {
@ -78,9 +79,15 @@ class FileExporter extends React.PureComponent<Props> {
}
render() {
const { label } = this.props;
const { label, disabled } = this.props;
return (
<Button button="primary" icon={ICONS.DOWNLOAD} label={label || __('Export')} onClick={this.handleButtonClick} />
<Button
button="primary"
disabled={disabled}
icon={ICONS.DOWNLOAD}
label={label || __('Export')}
onClick={this.handleButtonClick}
/>
);
}
}

View file

@ -8,6 +8,7 @@ import {
doSetTransactionListFilter,
selectIsFetchingTransactions,
} from 'lbry-redux';
import { withRouter } from 'react-router';
import TransactionList from './view';
const select = state => ({
@ -23,7 +24,9 @@ const perform = dispatch => ({
setTransactionFilter: filterSetting => dispatch(doSetTransactionListFilter(filterSetting)),
});
export default connect(
select,
perform
)(TransactionList);
export default withRouter(
connect(
select,
perform
)(TransactionList)
);

View file

@ -1,156 +1,110 @@
// @flow
import * as icons from 'constants/icons';
import * as MODALS from 'constants/modal_types';
import * as React from 'react';
import React from 'react';
import { FormField } from 'component/common/form';
import Button from 'component/button';
import FileExporter from 'component/common/file-exporter';
import { TRANSACTIONS } from 'lbry-redux';
import TransactionListItem from './internal/transaction-list-item';
import TransactionListTable from 'component/transactionListTable';
import RefreshTransactionButton from 'component/transactionRefreshButton';
import Spinner from 'component/spinner';
import Paginate from 'component/common/paginate';
type Props = {
emptyMessage: ?string,
filterSetting: string,
loading: boolean,
mySupports: {},
myClaims: any,
openModal: (id: string, { nout: number, txid: string }) => void,
rewards: {},
setTransactionFilter: string => void,
slim?: boolean,
title: string,
transactions: Array<Transaction>,
transactionCount?: number,
history: { replace: string => void },
};
class TransactionList extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
function TransactionList(props: Props) {
const { emptyMessage, slim, filterSetting, title, transactions, loading, history, transactionCount } = props;
const PAGE_SIZE = 20;
// Flow offers little support for Object.values() typing.
// https://github.com/facebook/flow/issues/2221
// $FlowFixMe
const transactionTypes: Array<string> = Object.values(TRANSACTIONS);
(this: any).handleFilterChanged = this.handleFilterChanged.bind(this);
(this: any).filterTransaction = this.filterTransaction.bind(this);
(this: any).revokeClaim = this.revokeClaim.bind(this);
(this: any).isRevokeable = this.isRevokeable.bind(this);
}
capitalize(string: string) {
function capitalize(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
handleFilterChanged(event: SyntheticInputEvent<*>) {
this.props.setTransactionFilter(event.target.value);
function handleFilterChanged(event: SyntheticInputEvent<*>) {
props.setTransactionFilter(event.target.value);
history.replace(`#/$/transactions`); //
}
filterTransaction(transaction: Transaction) {
return this.props.filterSetting === TRANSACTIONS.ALL || this.props.filterSetting === transaction.type;
}
isRevokeable(txid: string, nout: number) {
const outpoint = `${txid}:${nout}`;
const { mySupports, myClaims } = this.props;
return !!mySupports[outpoint] || myClaims.has(outpoint);
}
revokeClaim(txid: string, nout: number) {
this.props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { txid, nout });
}
render() {
const { emptyMessage, rewards, transactions, slim, filterSetting, title, loading } = this.props;
// The shorter "recent transactions" list shouldn't be filtered
const transactionList = slim ? transactions : transactions.filter(this.filterTransaction);
// Flow offers little support for Object.values() typing.
// https://github.com/facebook/flow/issues/2221
// $FlowFixMe
const transactionTypes: Array<string> = Object.values(TRANSACTIONS);
return (
<React.Fragment>
return (
<React.Fragment>
<header className="table__header">
<h2 className="card__title--between">
<span>
{title}
{loading && <Spinner type="small" />}
</span>
<div className="card__actions--inline">
{slim && (
<Button button="link" className="button--alt" navigate="/$/transactions" label={__('Full History')} />
)}
<RefreshTransactionButton />
</div>
</h2>
</header>
{!slim && (
<header className="table__header">
<h2 className="card__title--between">
<span>
{title}
{loading && <Spinner type="small" />}
</span>
<div className="card__actions--inline">
{slim && (
<Button button="link" className="button--alt" navigate="/$/transactions" label={__('Full History')} />
)}
<RefreshTransactionButton />
</div>
</h2>
<div className="card__actions--between">
<FileExporter
data={transactions}
label={__('Export')}
title={__('Export Transactions')}
filters={['nout']}
defaultPath={__('lbry-transactions-history')}
disabled={!transactions.length}
/>
<FormField
type="select"
name="file-sort"
value={filterSetting || TRANSACTIONS.ALL}
onChange={handleFilterChanged}
label={__('Show')}
postfix={
<Button
button="link"
icon={icons.HELP}
href="https://lbry.com/faq/transaction-types"
title={__('Help')}
/>
}
>
{transactionTypes.map(tt => (
<option key={tt} value={tt}>
{__(`${capitalize(tt)}`)}
</option>
))}
</FormField>
</div>
</header>
{!slim && !!transactions.length && (
<header className="table__header">
<div className="card__actions--between">
<FileExporter
data={transactionList}
label={__('Export')}
title={__('Export Transactions')}
filters={['nout']}
defaultPath={__('lbry-transactions-history')}
/>
)}
<FormField
type="select"
name="file-sort"
value={filterSetting || TRANSACTIONS.ALL}
onChange={this.handleFilterChanged}
label={__('Show')}
postfix={
<Button
button="link"
icon={icons.HELP}
href="https://lbry.com/faq/transaction-types"
title={__('Help')}
/>
}
>
{transactionTypes.map(tt => (
<option key={tt} value={tt}>
{__(`${this.capitalize(tt)}`)}
</option>
))}
</FormField>
</div>
</header>
)}
{!loading && !transactions.length && (
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
)}
{!loading && !transactionList.length && (
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
)}
{!!transactionList.length && (
<React.Fragment>
<table className="table table--transactions">
<thead>
<tr>
<th>{__('Amount')}</th>
<th>{__('Type')} </th>
<th>{__('Details')} </th>
<th>{__('Transaction')}</th>
<th>{__('Date')}</th>
</tr>
</thead>
<tbody>
{transactionList.map(t => (
<TransactionListItem
key={`${t.txid}:${t.nout}`}
transaction={t}
reward={rewards && rewards[t.txid]}
isRevokeable={this.isRevokeable(t.txid, t.nout)}
revokeClaim={this.revokeClaim}
/>
))}
</tbody>
</table>
</React.Fragment>
)}
</React.Fragment>
);
}
{!!transactions && !!transactions.length && <TransactionListTable transactionList={transactions} />}
{!slim && !!transactionCount && (
<Paginate
onPageChange={page => history.replace(`#/$/transactions?page=${Number(page)}`)}
totalPages={Math.ceil(transactionCount / PAGE_SIZE)}
/>
)}
</React.Fragment>
);
}
export default TransactionList;

View file

@ -0,0 +1,27 @@
import { connect } from 'react-redux';
import { selectClaimedRewardsByTransactionId } from 'lbryinc';
import { doOpenModal } from 'redux/actions/app';
import {
selectAllMyClaimsByOutpoint,
selectSupportsByOutpoint,
selectTransactionListFilter,
selectIsFetchingTransactions,
} from 'lbry-redux';
import TransactionListTable from './view';
const select = state => ({
rewards: selectClaimedRewardsByTransactionId(state),
mySupports: selectSupportsByOutpoint(state),
myClaims: selectAllMyClaimsByOutpoint(state),
filterSetting: selectTransactionListFilter(state),
loading: selectIsFetchingTransactions(state),
});
const perform = dispatch => ({
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
});
export default connect(
select,
perform
)(TransactionListTable);

View file

@ -0,0 +1,65 @@
// @flow
import * as MODALS from 'constants/modal_types';
import React from 'react';
import TransactionListItem from './internal/transaction-list-item';
type Props = {
emptyMessage: ?string,
loading: boolean,
mySupports: {},
myClaims: any,
openModal: (id: string, { nout: number, txid: string }) => void,
rewards: {},
transactionList: Array<any>,
};
// class TransactionList extends React.PureComponent<Props> {
function TransactionListTable(props: Props) {
const { emptyMessage, rewards, loading, transactionList } = props;
function isRevokeable(txid: string, nout: number) {
const outpoint = `${txid}:${nout}`;
const { mySupports, myClaims } = props;
return !!mySupports[outpoint] || myClaims.has(outpoint);
}
function revokeClaim(txid: string, nout: number) {
props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { txid, nout });
}
return (
<React.Fragment>
{!loading && !transactionList.length && (
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
)}
{!!transactionList.length && (
<React.Fragment>
<table className="table table--transactions">
<thead>
<tr>
<th>{__('Amount')}</th>
<th>{__('Type')} </th>
<th>{__('Details')} </th>
<th>{__('Transaction')}</th>
<th>{__('Date')}</th>
</tr>
</thead>
<tbody>
{transactionList.map(t => (
<TransactionListItem
key={`${t.txid}:${t.nout}`}
transaction={t}
reward={rewards && rewards[t.txid]}
isRevokeable={isRevokeable(t.txid, t.nout)}
revokeClaim={revokeClaim}
/>
))}
</tbody>
</table>
</React.Fragment>
)}
</React.Fragment>
);
}
export default TransactionListTable;

View file

@ -1,17 +1,33 @@
import { connect } from 'react-redux';
import { doFetchTransactions, selectTransactionItems, doFetchClaimListMine } from 'lbry-redux';
import {
doFetchTransactions,
doFetchClaimListMine,
makeSelectFilteredTransactionsForPage,
selectFilteredTransactionCount,
} from 'lbry-redux';
import { withRouter } from 'react-router';
import TransactionHistoryPage from './view';
const select = state => ({
transactions: selectTransactionItems(state),
});
const select = (state, props) => {
const { search } = props.location;
const urlParams = new URLSearchParams(search);
const page = Number(urlParams.get('page')) || 1;
return {
page,
filteredTransactionPage: makeSelectFilteredTransactionsForPage(page)(state),
filteredTransactionsCount: selectFilteredTransactionCount(state),
};
};
const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()),
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
});
export default connect(
select,
perform
)(TransactionHistoryPage);
export default withRouter(
connect(
select,
perform
)(TransactionHistoryPage)
);

View file

@ -9,7 +9,8 @@ type Props = {
fetchMyClaims: () => void,
fetchTransactions: () => void,
fetchingTransactions: boolean,
transactions: Array<{}>,
filteredTransactionPage: Array<{}>,
filteredTransactionsCount: number,
};
class TransactionHistoryPage extends React.PureComponent<Props> {
@ -21,7 +22,7 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
}
render() {
const { transactions } = this.props;
const { filteredTransactionPage, filteredTransactionsCount } = this.props;
return (
<Page>
@ -31,7 +32,11 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
'card--disabled': IS_WEB,
})}
>
<TransactionList transactions={transactions} title={__('Transaction History')} />
<TransactionList
transactions={filteredTransactionPage}
transactionCount={filteredTransactionsCount}
title={__('Transaction History')}
/>
</section>
</Page>
);