paginates transactions
This commit is contained in:
parent
534d84ef88
commit
5078bd6699
8 changed files with 217 additions and 140 deletions
|
@ -17,6 +17,7 @@ type Props = {
|
||||||
defaultPath?: string,
|
defaultPath?: string,
|
||||||
filters: Array<string>,
|
filters: Array<string>,
|
||||||
onFileCreated?: string => void,
|
onFileCreated?: string => void,
|
||||||
|
disabled: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileExporter extends React.PureComponent<Props> {
|
class FileExporter extends React.PureComponent<Props> {
|
||||||
|
@ -78,9 +79,15 @@ class FileExporter extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { label } = this.props;
|
const { label, disabled } = this.props;
|
||||||
return (
|
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}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
doSetTransactionListFilter,
|
doSetTransactionListFilter,
|
||||||
selectIsFetchingTransactions,
|
selectIsFetchingTransactions,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import TransactionList from './view';
|
import TransactionList from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -23,7 +24,9 @@ const perform = dispatch => ({
|
||||||
setTransactionFilter: filterSetting => dispatch(doSetTransactionListFilter(filterSetting)),
|
setTransactionFilter: filterSetting => dispatch(doSetTransactionListFilter(filterSetting)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default withRouter(
|
||||||
select,
|
connect(
|
||||||
perform
|
select,
|
||||||
)(TransactionList);
|
perform
|
||||||
|
)(TransactionList)
|
||||||
|
);
|
||||||
|
|
|
@ -1,156 +1,110 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import React from 'react';
|
||||||
import * as React from 'react';
|
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import FileExporter from 'component/common/file-exporter';
|
import FileExporter from 'component/common/file-exporter';
|
||||||
import { TRANSACTIONS } from 'lbry-redux';
|
import { TRANSACTIONS } from 'lbry-redux';
|
||||||
import TransactionListItem from './internal/transaction-list-item';
|
import TransactionListTable from 'component/transactionListTable';
|
||||||
import RefreshTransactionButton from 'component/transactionRefreshButton';
|
import RefreshTransactionButton from 'component/transactionRefreshButton';
|
||||||
import Spinner from 'component/spinner';
|
import Spinner from 'component/spinner';
|
||||||
|
import Paginate from 'component/common/paginate';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
emptyMessage: ?string,
|
emptyMessage: ?string,
|
||||||
filterSetting: string,
|
filterSetting: string,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
mySupports: {},
|
|
||||||
myClaims: any,
|
myClaims: any,
|
||||||
openModal: (id: string, { nout: number, txid: string }) => void,
|
|
||||||
rewards: {},
|
|
||||||
setTransactionFilter: string => void,
|
setTransactionFilter: string => void,
|
||||||
slim?: boolean,
|
slim?: boolean,
|
||||||
title: string,
|
title: string,
|
||||||
transactions: Array<Transaction>,
|
transactions: Array<Transaction>,
|
||||||
|
transactionCount?: number,
|
||||||
|
history: { replace: string => void },
|
||||||
};
|
};
|
||||||
|
|
||||||
class TransactionList extends React.PureComponent<Props> {
|
function TransactionList(props: Props) {
|
||||||
constructor(props: Props) {
|
const { emptyMessage, slim, filterSetting, title, transactions, loading, history, transactionCount } = props;
|
||||||
super(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);
|
function capitalize(string: string) {
|
||||||
(this: any).filterTransaction = this.filterTransaction.bind(this);
|
|
||||||
(this: any).revokeClaim = this.revokeClaim.bind(this);
|
|
||||||
(this: any).isRevokeable = this.isRevokeable.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
capitalize(string: string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFilterChanged(event: SyntheticInputEvent<*>) {
|
function handleFilterChanged(event: SyntheticInputEvent<*>) {
|
||||||
this.props.setTransactionFilter(event.target.value);
|
props.setTransactionFilter(event.target.value);
|
||||||
|
history.replace(`#/$/transactions`); //
|
||||||
}
|
}
|
||||||
|
|
||||||
filterTransaction(transaction: Transaction) {
|
return (
|
||||||
return this.props.filterSetting === TRANSACTIONS.ALL || this.props.filterSetting === transaction.type;
|
<React.Fragment>
|
||||||
}
|
<header className="table__header">
|
||||||
|
<h2 className="card__title--between">
|
||||||
isRevokeable(txid: string, nout: number) {
|
<span>
|
||||||
const outpoint = `${txid}:${nout}`;
|
{title}
|
||||||
const { mySupports, myClaims } = this.props;
|
{loading && <Spinner type="small" />}
|
||||||
return !!mySupports[outpoint] || myClaims.has(outpoint);
|
</span>
|
||||||
}
|
<div className="card__actions--inline">
|
||||||
|
{slim && (
|
||||||
revokeClaim(txid: string, nout: number) {
|
<Button button="link" className="button--alt" navigate="/$/transactions" label={__('Full History')} />
|
||||||
this.props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { txid, nout });
|
)}
|
||||||
}
|
<RefreshTransactionButton />
|
||||||
|
</div>
|
||||||
render() {
|
</h2>
|
||||||
const { emptyMessage, rewards, transactions, slim, filterSetting, title, loading } = this.props;
|
</header>
|
||||||
// The shorter "recent transactions" list shouldn't be filtered
|
{!slim && (
|
||||||
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>
|
|
||||||
<header className="table__header">
|
<header className="table__header">
|
||||||
<h2 className="card__title--between">
|
<div className="card__actions--between">
|
||||||
<span>
|
<FileExporter
|
||||||
{title}
|
data={transactions}
|
||||||
{loading && <Spinner type="small" />}
|
label={__('Export')}
|
||||||
</span>
|
title={__('Export Transactions')}
|
||||||
<div className="card__actions--inline">
|
filters={['nout']}
|
||||||
{slim && (
|
defaultPath={__('lbry-transactions-history')}
|
||||||
<Button button="link" className="button--alt" navigate="/$/transactions" label={__('Full History')} />
|
disabled={!transactions.length}
|
||||||
)}
|
/>
|
||||||
<RefreshTransactionButton />
|
<FormField
|
||||||
</div>
|
type="select"
|
||||||
</h2>
|
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>
|
</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
|
{!loading && !transactions.length && (
|
||||||
type="select"
|
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
|
||||||
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 && !transactionList.length && (
|
{!!transactions && !!transactions.length && <TransactionListTable transactionList={transactions} />}
|
||||||
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
|
{!slim && !!transactionCount && (
|
||||||
)}
|
<Paginate
|
||||||
|
onPageChange={page => history.replace(`#/$/transactions?page=${Number(page)}`)}
|
||||||
{!!transactionList.length && (
|
totalPages={Math.ceil(transactionCount / PAGE_SIZE)}
|
||||||
<React.Fragment>
|
/>
|
||||||
<table className="table table--transactions">
|
)}
|
||||||
<thead>
|
</React.Fragment>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TransactionList;
|
export default TransactionList;
|
||||||
|
|
27
src/ui/component/transactionListTable/index.js
Normal file
27
src/ui/component/transactionListTable/index.js
Normal 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);
|
65
src/ui/component/transactionListTable/view.jsx
Normal file
65
src/ui/component/transactionListTable/view.jsx
Normal 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;
|
|
@ -1,17 +1,33 @@
|
||||||
import { connect } from 'react-redux';
|
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';
|
import TransactionHistoryPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state, props) => {
|
||||||
transactions: selectTransactionItems(state),
|
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 => ({
|
const perform = dispatch => ({
|
||||||
fetchTransactions: () => dispatch(doFetchTransactions()),
|
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||||
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
|
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default withRouter(
|
||||||
select,
|
connect(
|
||||||
perform
|
select,
|
||||||
)(TransactionHistoryPage);
|
perform
|
||||||
|
)(TransactionHistoryPage)
|
||||||
|
);
|
||||||
|
|
|
@ -9,7 +9,8 @@ type Props = {
|
||||||
fetchMyClaims: () => void,
|
fetchMyClaims: () => void,
|
||||||
fetchTransactions: () => void,
|
fetchTransactions: () => void,
|
||||||
fetchingTransactions: boolean,
|
fetchingTransactions: boolean,
|
||||||
transactions: Array<{}>,
|
filteredTransactionPage: Array<{}>,
|
||||||
|
filteredTransactionsCount: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TransactionHistoryPage extends React.PureComponent<Props> {
|
class TransactionHistoryPage extends React.PureComponent<Props> {
|
||||||
|
@ -21,7 +22,7 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { transactions } = this.props;
|
const { filteredTransactionPage, filteredTransactionsCount } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
@ -31,7 +32,11 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
|
||||||
'card--disabled': IS_WEB,
|
'card--disabled': IS_WEB,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<TransactionList transactions={transactions} title={__('Transaction History')} />
|
<TransactionList
|
||||||
|
transactions={filteredTransactionPage}
|
||||||
|
transactionCount={filteredTransactionsCount}
|
||||||
|
title={__('Transaction History')}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue