use txo list for wallet page:
paginated enable revoking filtering txo pagination changes move constants remove fetchTransactions() calls review changes final changes
This commit is contained in:
parent
a6000ecf0b
commit
fdd20ef350
30 changed files with 617 additions and 556 deletions
|
@ -130,7 +130,7 @@
|
||||||
"imagesloaded": "^4.1.4",
|
"imagesloaded": "^4.1.4",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#677dd256437f4cefa3eba96f0a28a814d07736c8",
|
"lbry-redux": "lbryio/lbry-redux#c51483f4171f0056da65193f979a483248a68297",
|
||||||
"lbryinc": "lbryio/lbryinc#12aefaa14343d2f3eac01f2683701f58e53f1848",
|
"lbryinc": "lbryio/lbryinc#12aefaa14343d2f3eac01f2683701f58e53f1848",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
|
|
@ -1128,5 +1128,48 @@
|
||||||
"%message% hihi": "%message% hihi",
|
"%message% hihi": "%message% hihi",
|
||||||
"How much would you like to unlock?": "How much would you like to unlock?",
|
"How much would you like to unlock?": "How much would you like to unlock?",
|
||||||
"A prudent choice": "A prudent choice",
|
"A prudent choice": "A prudent choice",
|
||||||
"Join": "Join"
|
"Join": "Join",
|
||||||
|
"File Details": "File Details",
|
||||||
|
"You can unlock all or some of this LBC at any time.": "You can unlock all or some of this LBC at any time.",
|
||||||
|
"Keeping it locked improves the trust and discoverability of your content.": "Keeping it locked improves the trust and discoverability of your content.",
|
||||||
|
"It's usually only worth unlocking what you intend to use immediately. %learn_more%": "It's usually only worth unlocking what you intend to use immediately. %learn_more%",
|
||||||
|
"%amount% available to unlock": "%amount% available to unlock",
|
||||||
|
"%message% hihi": "%message% hihi",
|
||||||
|
"Comrade Yrbl, we have a problem": "Comrade Yrbl, we have a problem",
|
||||||
|
"How much would you like to unlock?": "How much would you like to unlock?",
|
||||||
|
"A prudent choice": "A prudent choice",
|
||||||
|
"Preparing your content": "Preparing your content",
|
||||||
|
"New History": "New History",
|
||||||
|
"Share on LinkedIn": "Share on LinkedIn",
|
||||||
|
"Embed this content": "Embed this content",
|
||||||
|
"More actions": "More actions",
|
||||||
|
"music": "music",
|
||||||
|
"Transactions": "Transactions",
|
||||||
|
"Tx Type": "Tx Type",
|
||||||
|
"Payment": "Payment",
|
||||||
|
"Stream": "Stream",
|
||||||
|
"Tips": "Tips",
|
||||||
|
"Done!": "Done!",
|
||||||
|
"Publish to %uri%": "Publish to %uri%",
|
||||||
|
"Sent": "Sent",
|
||||||
|
"Received": "Received",
|
||||||
|
"active": "active",
|
||||||
|
"spent": "spent",
|
||||||
|
"all": "all",
|
||||||
|
"Payment Type": "Payment Type",
|
||||||
|
"Purchase": "Purchase",
|
||||||
|
"No transactions.": "No transactions.",
|
||||||
|
"Active": "Active",
|
||||||
|
"Historical": "Historical",
|
||||||
|
"Reposts": "Reposts",
|
||||||
|
"lbry": "lbry",
|
||||||
|
"Default": "Default",
|
||||||
|
"chillstep": "chillstep",
|
||||||
|
"economics": "economics",
|
||||||
|
"education": "education",
|
||||||
|
"linux": "linux",
|
||||||
|
"math": "math",
|
||||||
|
"news": "news",
|
||||||
|
"science": "science",
|
||||||
|
"Amount (LBC)": "Amount (LBC)"
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ import {
|
||||||
doUserSetReferrer,
|
doUserSetReferrer,
|
||||||
selectUserVerifiedEmail,
|
selectUserVerifiedEmail,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import { doFetchTransactions, doFetchChannelListMine } from 'lbry-redux';
|
import { doFetchChannelListMine } from 'lbry-redux';
|
||||||
import { makeSelectClientSetting, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||||
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
|
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
|
||||||
import { doSetLanguage } from 'redux/actions/settings';
|
import { doSetLanguage } from 'redux/actions/settings';
|
||||||
|
@ -41,7 +41,6 @@ const select = state => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
analyticsTagSync: () => dispatch(doAnalyticsTagSync()),
|
analyticsTagSync: () => dispatch(doAnalyticsTagSync()),
|
||||||
fetchTransactions: (page, pageSize) => dispatch(doFetchTransactions(page, pageSize)),
|
|
||||||
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
fetchAccessToken: () => dispatch(doFetchAccessToken()),
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||||
setLanguage: language => dispatch(doSetLanguage(language)),
|
setLanguage: language => dispatch(doSetLanguage(language)),
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as PAGES from 'constants/pages';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import { buildURI, parseURI, TX_LIST } from 'lbry-redux';
|
import { buildURI, parseURI } from 'lbry-redux';
|
||||||
import Router from 'component/router/index';
|
import Router from 'component/router/index';
|
||||||
import ModalRouter from 'modal/modalRouter';
|
import ModalRouter from 'modal/modalRouter';
|
||||||
import ReactModal from 'react-modal';
|
import ReactModal from 'react-modal';
|
||||||
|
@ -54,7 +54,6 @@ type Props = {
|
||||||
length: number,
|
length: number,
|
||||||
push: string => void,
|
push: string => void,
|
||||||
},
|
},
|
||||||
fetchTransactions: (number, number) => void,
|
|
||||||
fetchAccessToken: () => void,
|
fetchAccessToken: () => void,
|
||||||
fetchChannelListMine: () => void,
|
fetchChannelListMine: () => void,
|
||||||
signIn: () => void,
|
signIn: () => void,
|
||||||
|
@ -78,7 +77,6 @@ type Props = {
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
const {
|
const {
|
||||||
theme,
|
theme,
|
||||||
fetchTransactions,
|
|
||||||
user,
|
user,
|
||||||
fetchAccessToken,
|
fetchAccessToken,
|
||||||
fetchChannelListMine,
|
fetchChannelListMine,
|
||||||
|
@ -178,10 +176,9 @@ function App(props: Props) {
|
||||||
fetchAccessToken();
|
fetchAccessToken();
|
||||||
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
fetchTransactions(1, TX_LIST.LATEST_PAGE_SIZE);
|
|
||||||
fetchChannelListMine(); // This needs to be done for web too...
|
fetchChannelListMine(); // This needs to be done for web too...
|
||||||
// @endif
|
// @endif
|
||||||
}, [appRef, fetchAccessToken, fetchChannelListMine, fetchTransactions]);
|
}, [appRef, fetchAccessToken, fetchChannelListMine]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as ICONS from 'constants/icons';
|
||||||
type Props = {
|
type Props = {
|
||||||
title?: string | Node,
|
title?: string | Node,
|
||||||
subtitle?: string | Node,
|
subtitle?: string | Node,
|
||||||
|
titleActions?: string | Node,
|
||||||
body?: string | Node,
|
body?: string | Node,
|
||||||
actions?: string | Node,
|
actions?: string | Node,
|
||||||
icon?: string,
|
icon?: string,
|
||||||
|
@ -24,6 +25,7 @@ export default function Card(props: Props) {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
|
titleActions,
|
||||||
body,
|
body,
|
||||||
actions,
|
actions,
|
||||||
icon,
|
icon,
|
||||||
|
@ -48,8 +50,10 @@ export default function Card(props: Props) {
|
||||||
{subtitle && <div className="card__subtitle">{subtitle}</div>}
|
{subtitle && <div className="card__subtitle">{subtitle}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{titleActions && <div className="card__title-actions">{titleActions}</div>}
|
||||||
{expandable && (
|
{expandable && (
|
||||||
<div className="card__expand-btn">
|
<div className="card__title-actions">
|
||||||
<Button
|
<Button
|
||||||
button={'alt'}
|
button={'alt'}
|
||||||
aria-label={__('More')}
|
aria-label={__('More')}
|
||||||
|
@ -59,6 +63,7 @@ export default function Card(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{(!expandable || (expandable && expanded)) && (
|
{(!expandable || (expandable && expanded)) && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -16,7 +16,6 @@ import InvitedPage from 'page/invited';
|
||||||
import RewardsPage from 'page/rewards';
|
import RewardsPage from 'page/rewards';
|
||||||
import FileListDownloaded from 'page/fileListDownloaded';
|
import FileListDownloaded from 'page/fileListDownloaded';
|
||||||
import FileListPublished from 'page/fileListPublished';
|
import FileListPublished from 'page/fileListPublished';
|
||||||
import TransactionHistoryPage from 'page/transactionHistory';
|
|
||||||
import InvitePage from 'page/invite';
|
import InvitePage from 'page/invite';
|
||||||
import SearchPage from 'page/search';
|
import SearchPage from 'page/search';
|
||||||
import LibraryPage from 'page/library';
|
import LibraryPage from 'page/library';
|
||||||
|
@ -183,9 +182,9 @@ function AppRouter(props: Props) {
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS_VERIFY}`} component={RewardsVerifyPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS_VERIFY}`} component={RewardsVerifyPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
|
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { selectClaimedRewardsByTransactionId } from 'lbryinc';
|
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
|
||||||
import {
|
|
||||||
selectAllMyClaimsByOutpoint,
|
|
||||||
selectSupportsByOutpoint,
|
|
||||||
selectTransactionListFilter,
|
|
||||||
doSetTransactionListFilter,
|
|
||||||
selectIsFetchingTransactions,
|
|
||||||
selectTransactionItems,
|
|
||||||
} from 'lbry-redux';
|
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
import TransactionList from './view';
|
|
||||||
|
|
||||||
const select = state => ({
|
|
||||||
rewards: selectClaimedRewardsByTransactionId(state),
|
|
||||||
mySupports: selectSupportsByOutpoint(state),
|
|
||||||
myClaims: selectAllMyClaimsByOutpoint(state),
|
|
||||||
filterSetting: selectTransactionListFilter(state),
|
|
||||||
loading: selectIsFetchingTransactions(state),
|
|
||||||
allTransactions: selectTransactionItems(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
|
||||||
setTransactionFilter: filterSetting => dispatch(doSetTransactionListFilter(filterSetting)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withRouter(
|
|
||||||
connect(
|
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(TransactionList)
|
|
||||||
);
|
|
|
@ -1,114 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as icons from 'constants/icons';
|
|
||||||
import React from 'react';
|
|
||||||
import { FormField } from 'component/common/form';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import FileExporter from 'component/common/file-exporter';
|
|
||||||
import { TRANSACTIONS, TX_LIST } from 'lbry-redux';
|
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import TransactionListTable from 'component/transactionListTable';
|
|
||||||
import RefreshTransactionButton from 'component/transactionRefreshButton';
|
|
||||||
import Paginate from 'component/common/paginate';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
emptyMessage: ?string,
|
|
||||||
filterSetting: string,
|
|
||||||
loading: boolean,
|
|
||||||
myClaims: any,
|
|
||||||
setTransactionFilter: string => void,
|
|
||||||
slim?: boolean,
|
|
||||||
title: string,
|
|
||||||
allTransactions: Array<Transaction>,
|
|
||||||
transactions: Array<Transaction>,
|
|
||||||
transactionCount?: number,
|
|
||||||
history: { replace: string => void },
|
|
||||||
};
|
|
||||||
|
|
||||||
function TransactionList(props: Props) {
|
|
||||||
const {
|
|
||||||
emptyMessage,
|
|
||||||
slim,
|
|
||||||
filterSetting,
|
|
||||||
title,
|
|
||||||
transactions,
|
|
||||||
loading,
|
|
||||||
history,
|
|
||||||
transactionCount,
|
|
||||||
allTransactions,
|
|
||||||
} = props;
|
|
||||||
// Flow offers little support for Object.values() typing.
|
|
||||||
// https://github.com/facebook/flow/issues/2221
|
|
||||||
// $FlowFixMe
|
|
||||||
const transactionTypes: Array<string> = Object.values(TRANSACTIONS);
|
|
||||||
|
|
||||||
function capitalize(string: string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFilterChanged(event: SyntheticInputEvent<*>) {
|
|
||||||
props.setTransactionFilter(event.target.value);
|
|
||||||
history.replace(`/$/${PAGES.TRANSACTIONS}`); //
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<header className="table__header">
|
|
||||||
<div className="table__header-text--between">
|
|
||||||
<h2 className="card__title">{title}</h2>
|
|
||||||
<div className="card__actions--inline">
|
|
||||||
<RefreshTransactionButton slim={slim} />
|
|
||||||
{/* @if TARGET='app' */}
|
|
||||||
{!slim && (
|
|
||||||
<FileExporter
|
|
||||||
data={allTransactions}
|
|
||||||
label={__('Export')}
|
|
||||||
title={__('Export Transactions')}
|
|
||||||
filters={['nout']}
|
|
||||||
defaultPath={'lbry-transactions-history'}
|
|
||||||
disabled={!transactions.length}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* @endif */}
|
|
||||||
{!slim && (
|
|
||||||
<FormField
|
|
||||||
type="select"
|
|
||||||
name="file-sort"
|
|
||||||
value={filterSetting || TRANSACTIONS.ALL}
|
|
||||||
onChange={handleFilterChanged}
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
{slim && <Button button="primary" navigate={`/$/${PAGES.TRANSACTIONS}`} label={__('Full History')} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{((loading && !transactions.length) || !transactions.length) && (
|
|
||||||
<h2 className="main--empty empty">{loading ? __('Loading') : emptyMessage || __('No transactions.')}</h2>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!!transactions && !!transactions.length && <TransactionListTable transactionList={transactions} />}
|
|
||||||
{!slim && !!transactionCount && (
|
|
||||||
<Paginate
|
|
||||||
onPageChange={page => history.replace(`/$/${PAGES.TRANSACTIONS}?page=${Number(page)}`)}
|
|
||||||
totalPages={Math.ceil(transactionCount / TX_LIST.PAGE_SIZE)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TransactionList;
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import {
|
|
||||||
doFetchTransactions,
|
|
||||||
makeSelectLatestTransactions,
|
|
||||||
doFetchClaimListMine,
|
|
||||||
selectMyClaimsWithoutChannels,
|
|
||||||
} from 'lbry-redux';
|
|
||||||
import TransactionListRecent from './view';
|
|
||||||
|
|
||||||
const select = state => {
|
|
||||||
return {
|
|
||||||
transactions: makeSelectLatestTransactions(state),
|
|
||||||
myClaims: selectMyClaimsWithoutChannels(state),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
fetchTransactions: (page, pageSize) => dispatch(doFetchTransactions(page, pageSize)),
|
|
||||||
fetchClaimListMine: () => dispatch(doFetchClaimListMine()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(TransactionListRecent);
|
|
|
@ -1,41 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import TransactionList from 'component/transactionList';
|
|
||||||
import { TX_LIST } from 'lbry-redux';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
fetchTransactions: (number, number) => void,
|
|
||||||
fetchClaimListMine: () => void,
|
|
||||||
fetchingTransactions: boolean,
|
|
||||||
hasTransactions: boolean,
|
|
||||||
transactions: Array<Transaction>,
|
|
||||||
myClaims: ?Array<StreamClaim>,
|
|
||||||
};
|
|
||||||
|
|
||||||
function TransactionListRecent(props: Props) {
|
|
||||||
const { transactions, fetchTransactions, myClaims, fetchClaimListMine } = props;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
fetchTransactions(1, TX_LIST.LATEST_PAGE_SIZE);
|
|
||||||
}, [fetchTransactions]);
|
|
||||||
|
|
||||||
const myClaimsString = myClaims && myClaims.map(channel => channel.permanent_url).join(',');
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (myClaimsString === '') {
|
|
||||||
fetchClaimListMine();
|
|
||||||
}
|
|
||||||
}, [myClaimsString, fetchClaimListMine]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="card">
|
|
||||||
<TransactionList
|
|
||||||
slim
|
|
||||||
title={__('Latest Transactions')}
|
|
||||||
transactions={transactions}
|
|
||||||
emptyMessage={__("Looks like you don't have any transactions.")}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TransactionListRecent;
|
|
|
@ -1,27 +1,16 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectClaimedRewardsByTransactionId } from 'lbryinc';
|
import { selectClaimedRewardsByTransactionId } from 'lbryinc';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import {
|
import { selectIsFetchingTxos } from 'lbry-redux';
|
||||||
selectAllMyClaimsByOutpoint,
|
|
||||||
selectSupportsByOutpoint,
|
|
||||||
selectTransactionListFilter,
|
|
||||||
selectIsFetchingTransactions,
|
|
||||||
} from 'lbry-redux';
|
|
||||||
import TransactionListTable from './view';
|
import TransactionListTable from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
rewards: selectClaimedRewardsByTransactionId(state),
|
rewards: selectClaimedRewardsByTransactionId(state),
|
||||||
mySupports: selectSupportsByOutpoint(state),
|
loading: selectIsFetchingTxos(state),
|
||||||
myClaims: selectAllMyClaimsByOutpoint(state),
|
|
||||||
filterSetting: selectTransactionListFilter(state),
|
|
||||||
loading: selectIsFetchingTransactions(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(TransactionListTable);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(TransactionListTable);
|
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as TXN_TYPES from 'constants/transaction_types';
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import React from 'react';
|
|
||||||
import ButtonTransaction from 'component/common/transaction-link';
|
|
||||||
import CreditAmount from 'component/common/credit-amount';
|
|
||||||
import DateTime from 'component/dateTime';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import { buildURI, parseURI } from 'lbry-redux';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
transaction: Transaction,
|
|
||||||
revokeClaim: (string, number) => void,
|
|
||||||
isRevokeable: boolean,
|
|
||||||
reward: ?{
|
|
||||||
reward_title: string,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
class TransactionListItem extends React.PureComponent<Props> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
(this: any).abandonClaim = this.abandonClaim.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
getLink(type: string) {
|
|
||||||
if (type === TXN_TYPES.TIP) {
|
|
||||||
return <Button button="secondary" icon={ICONS.UNLOCK} onClick={this.abandonClaim} title={__('Unlock Tip')} />;
|
|
||||||
}
|
|
||||||
const abandonTitle = type === TXN_TYPES.SUPPORT ? 'Abandon Support' : 'Abandon Claim';
|
|
||||||
return <Button button="secondary" icon={ICONS.DELETE} onClick={this.abandonClaim} title={__(abandonTitle)} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
abandonClaim() {
|
|
||||||
const { txid, nout } = this.props.transaction;
|
|
||||||
|
|
||||||
this.props.revokeClaim(txid, nout);
|
|
||||||
}
|
|
||||||
|
|
||||||
capitalize = (string: ?string) => string && string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { reward, transaction, isRevokeable } = this.props;
|
|
||||||
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
|
|
||||||
|
|
||||||
// Ensure the claim name exists and is valid
|
|
||||||
let uri;
|
|
||||||
let claimName;
|
|
||||||
try {
|
|
||||||
if (name.startsWith('@')) {
|
|
||||||
({ claimName } = parseURI(name));
|
|
||||||
uri = buildURI({ channelName: claimName, channelClaimId: claimId });
|
|
||||||
} else {
|
|
||||||
({ claimName } = parseURI(name));
|
|
||||||
uri = buildURI({ streamName: claimName, streamClaimId: claimId });
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const dateFormat = {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
};
|
|
||||||
|
|
||||||
const forClaim = name && claimId;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{date ? (
|
|
||||||
<div>
|
|
||||||
<DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
|
|
||||||
<div className="table__item-label">
|
|
||||||
<DateTime date={date} show={DateTime.SHOW_TIME} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="empty">{__('Pending')}</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td className="table__item--actionable">
|
|
||||||
<span>{this.capitalize(type)}</span> {isRevokeable && this.getLink(type)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{forClaim && <Button button="link" navigate={uri} label={claimName} disabled={!date} />}
|
|
||||||
{!forClaim && reward && <span>{reward.reward_title}</span>}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<ButtonTransaction id={txid} />
|
|
||||||
</td>
|
|
||||||
<td className="table__item--align-right">
|
|
||||||
<CreditAmount badge={false} showPlus amount={amount} precision={8} />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
{fee !== 0 && (
|
|
||||||
<span className="table__item-label">
|
|
||||||
<CreditAmount badge={false} fee amount={fee} precision={8} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TransactionListItem;
|
|
147
ui/component/transactionListTable/internal/txo-list-item.jsx
Normal file
147
ui/component/transactionListTable/internal/txo-list-item.jsx
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
// @flow
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import ButtonTransaction from 'component/common/transaction-link';
|
||||||
|
import CreditAmount from 'component/common/credit-amount';
|
||||||
|
import DateTime from 'component/dateTime';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import { buildURI, parseURI, TXO_LIST as TXO } from 'lbry-redux';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
txo: Txo,
|
||||||
|
revokeClaim: (Txo, () => void) => void,
|
||||||
|
isRevokeable: boolean,
|
||||||
|
reward: ?{
|
||||||
|
reward_title: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
disabled: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TxoListItem extends React.PureComponent<Props, State> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = { disabled: false };
|
||||||
|
(this: any).abandonClaim = this.abandonClaim.bind(this);
|
||||||
|
(this: any).getLink = this.getLink.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLink(type: string) {
|
||||||
|
if (type === TXO.SUPPORT) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
disabled={this.state.disabled}
|
||||||
|
button="secondary"
|
||||||
|
icon={ICONS.UNLOCK}
|
||||||
|
onClick={this.abandonClaim}
|
||||||
|
title={__('Unlock Tip')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const abandonTitle = type === TXO.SUPPORT ? 'Abandon Support' : 'Abandon Claim';
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
disabled={this.state.disabled}
|
||||||
|
button="secondary"
|
||||||
|
icon={ICONS.DELETE}
|
||||||
|
onClick={this.abandonClaim}
|
||||||
|
title={__(abandonTitle)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
abandonClaim() {
|
||||||
|
this.props.revokeClaim(this.props.txo, () => this.setState({ disabled: true }));
|
||||||
|
// this.setState({ disabled: true }); // just flag the item disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
capitalize = (string: string) => string && string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { reward, txo, isRevokeable } = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
amount,
|
||||||
|
claim_id: claimId,
|
||||||
|
normalized_name: txoListName,
|
||||||
|
timestamp,
|
||||||
|
txid,
|
||||||
|
type,
|
||||||
|
value_type: valueType,
|
||||||
|
is_my_input: isMyInput, // no transaction
|
||||||
|
is_my_output: isMyOutput,
|
||||||
|
} = txo;
|
||||||
|
|
||||||
|
const name = txoListName;
|
||||||
|
const isMinus = (type === 'support' || type === 'payment' || type === 'other') && isMyInput && !isMyOutput;
|
||||||
|
const isTip = type === 'support' && ((isMyInput && !isMyOutput) || (!isMyInput && isMyOutput));
|
||||||
|
const date = new Date(timestamp * 1000);
|
||||||
|
|
||||||
|
// Ensure the claim name exists and is valid
|
||||||
|
let uri;
|
||||||
|
let claimName;
|
||||||
|
try {
|
||||||
|
if (name.startsWith('@')) {
|
||||||
|
({ claimName } = parseURI(name));
|
||||||
|
uri = buildURI({ channelName: claimName, channelClaimId: claimId });
|
||||||
|
} else {
|
||||||
|
({ claimName } = parseURI(name));
|
||||||
|
uri = buildURI({ streamName: claimName, streamClaimId: claimId });
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
const dateFormat = {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
};
|
||||||
|
|
||||||
|
const forClaim = name && claimId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{timestamp ? (
|
||||||
|
<div>
|
||||||
|
<DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
|
||||||
|
<div className="table__item-label">
|
||||||
|
<DateTime date={date} show={DateTime.SHOW_TIME} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="empty">{__('Pending')}</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="table__item--actionable">
|
||||||
|
<span>
|
||||||
|
{(isTip && __(this.capitalize('tip'))) ||
|
||||||
|
(valueType && __(this.capitalize(valueType))) ||
|
||||||
|
(type && __(this.capitalize(type)))}
|
||||||
|
</span>{' '}
|
||||||
|
{isRevokeable && this.getLink(type)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{forClaim && <Button button="link" navigate={uri} label={claimName} disabled={!date} />}
|
||||||
|
{!forClaim && reward && <span>{reward.reward_title}</span>}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<ButtonTransaction id={txid} />
|
||||||
|
</td>
|
||||||
|
<td className="table__item--align-right">
|
||||||
|
<CreditAmount
|
||||||
|
badge={false}
|
||||||
|
showPlus={isMinus}
|
||||||
|
amount={isMinus ? Number(0 - amount) : Number(amount)}
|
||||||
|
precision={8}
|
||||||
|
showLBC={false}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TxoListItem;
|
|
@ -1,38 +1,32 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TransactionListItem from './internal/transaction-list-item';
|
import TxoListItem from './internal/txo-list-item';
|
||||||
|
import Spinner from 'component/spinner';
|
||||||
type Props = {
|
type Props = {
|
||||||
emptyMessage: ?string,
|
emptyMessage: ?string,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
mySupports: {},
|
openModal: (id: string, { tx: Txo }) => void,
|
||||||
myClaims: any,
|
|
||||||
openModal: (id: string, { nout: number, txid: string }) => void,
|
|
||||||
rewards: {},
|
rewards: {},
|
||||||
transactionList: Array<any>,
|
txos: Array<Txo>,
|
||||||
};
|
};
|
||||||
|
|
||||||
function TransactionListTable(props: Props) {
|
function TransactionListTable(props: Props) {
|
||||||
const { emptyMessage, rewards, loading, transactionList } = props;
|
const { emptyMessage, rewards, loading, txos } = props;
|
||||||
|
const REVOCABLE_TYPES = ['channel', 'stream', 'repost', 'support', 'claim'];
|
||||||
function isRevokeable(txid: string, nout: number) {
|
function revokeClaim(tx: any, cb: () => void) {
|
||||||
const outpoint = `${txid}:${nout}`;
|
props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { tx, cb });
|
||||||
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 (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{!loading && !transactionList.length && (
|
{!loading && !txos.length && <h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>}
|
||||||
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
|
{loading && (
|
||||||
|
<h2 className="main--empty empty">
|
||||||
|
<Spinner delayed />
|
||||||
|
</h2>
|
||||||
)}
|
)}
|
||||||
{!!transactionList.length && (
|
{!loading && !!txos.length && (
|
||||||
<div className="table__wrapper">
|
<div className="table__wrapper">
|
||||||
<table className="table table--transactions">
|
<table className="table table--transactions">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -41,16 +35,17 @@ function TransactionListTable(props: Props) {
|
||||||
<th>{__('Type')} </th>
|
<th>{__('Type')} </th>
|
||||||
<th>{__('Details')} </th>
|
<th>{__('Details')} </th>
|
||||||
<th>{__('Transaction')}</th>
|
<th>{__('Transaction')}</th>
|
||||||
<th className="table__item--align-right">{__('Amount')}</th>
|
<th className="table__item--align-right">{__('Amount (LBC)')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{transactionList.map(t => (
|
{txos &&
|
||||||
<TransactionListItem
|
txos.map((t, i) => (
|
||||||
key={`${t.txid}:${t.nout}`}
|
<TxoListItem
|
||||||
transaction={t}
|
key={`${t.txid}:${t.nout}-${i}`}
|
||||||
|
txo={t}
|
||||||
reward={rewards && rewards[t.txid]}
|
reward={rewards && rewards[t.txid]}
|
||||||
isRevokeable={isRevokeable(t.txid, t.nout)}
|
isRevokeable={t.is_my_output && !t.is_spent && REVOCABLE_TYPES.includes(t.type)}
|
||||||
revokeClaim={revokeClaim}
|
revokeClaim={revokeClaim}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { doFetchTransactions, selectIsFetchingTransactions } from 'lbry-redux';
|
|
||||||
import RefreshTransactionButton from './view';
|
|
||||||
|
|
||||||
const select = state => ({
|
|
||||||
fetchingTransactions: selectIsFetchingTransactions(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
fetchTransactions: (page, pageSize) => dispatch(doFetchTransactions(page, pageSize)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(RefreshTransactionButton);
|
|
|
@ -1,53 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React, { PureComponent } from 'react';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import { TX_LIST } from 'lbry-redux';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
fetchTransactions: (?number, ?number) => void,
|
|
||||||
fetchingTransactions: boolean,
|
|
||||||
slim: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
label: string,
|
|
||||||
disabled: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
class TransactionRefreshButton extends PureComponent<Props, State> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.state = { label: __('Refresh'), disabled: false };
|
|
||||||
|
|
||||||
(this: any).handleClick = this.handleClick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick() {
|
|
||||||
const { fetchTransactions, slim } = this.props;
|
|
||||||
|
|
||||||
// The fetchTransactions call will be super fast most of the time.
|
|
||||||
// Instead of showing a loading spinner for 100ms, change the label and show as "Refreshed!"
|
|
||||||
if (slim) {
|
|
||||||
fetchTransactions(1, TX_LIST.LATEST_PAGE_SIZE);
|
|
||||||
} else {
|
|
||||||
fetchTransactions();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ label: __('Refreshed!'), disabled: true });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({ label: __('Refresh'), disabled: false });
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { fetchingTransactions } = this.props;
|
|
||||||
const { label, disabled } = this.state;
|
|
||||||
return (
|
|
||||||
<Button button="secondary" label={label} onClick={this.handleClick} disabled={disabled || fetchingTransactions} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TransactionRefreshButton;
|
|
29
ui/component/txoList/index.js
Normal file
29
ui/component/txoList/index.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
|
import {
|
||||||
|
selectIsFetchingTxos,
|
||||||
|
selectFetchingTxosError,
|
||||||
|
selectTxoPage,
|
||||||
|
selectTxoPageNumber,
|
||||||
|
selectTxoItemCount,
|
||||||
|
doFetchTxoPage,
|
||||||
|
doUpdateTxoPageParams,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
import TxoList from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
txoFetchError: selectFetchingTxosError(state),
|
||||||
|
txoPage: selectTxoPage(state),
|
||||||
|
txoPageNumber: selectTxoPageNumber(state),
|
||||||
|
txoItemCount: selectTxoItemCount(state),
|
||||||
|
loading: selectIsFetchingTxos(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
|
fetchTxoPage: () => dispatch(doFetchTxoPage()),
|
||||||
|
updateTxoPageParams: params => dispatch(doUpdateTxoPageParams(params)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withRouter(connect(select, perform)(TxoList));
|
260
ui/component/txoList/view.jsx
Normal file
260
ui/component/txoList/view.jsx
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
// @flow
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
import { TXO_LIST as TXO } from 'lbry-redux';
|
||||||
|
import TransactionListTable from 'component/transactionListTable';
|
||||||
|
import Paginate from 'component/common/paginate';
|
||||||
|
import { FormField } from 'component/common/form-components/form-field';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
import { toCapitalCase } from 'util/string';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
search: string,
|
||||||
|
history: { action: string, push: string => void, replace: string => void },
|
||||||
|
txoPage: Array<Transaction>,
|
||||||
|
txoPageNumber: string,
|
||||||
|
txoItemCount: number,
|
||||||
|
fetchTxoPage: () => void,
|
||||||
|
updateTxoPageParams: any => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Delta = {
|
||||||
|
dkey: string,
|
||||||
|
value: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function TxoList(props: Props) {
|
||||||
|
const { search, txoPage, txoItemCount, fetchTxoPage, updateTxoPageParams, history } = props;
|
||||||
|
|
||||||
|
console.log('txoPage', txoPage);
|
||||||
|
// parse urlParams
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const page = urlParams.get(TXO.PAGE) || String(1);
|
||||||
|
const pageSize = urlParams.get(TXO.PAGE_SIZE) || String(TXO.PAGE_SIZE_DEFAULT);
|
||||||
|
const type = urlParams.get(TXO.TYPE);
|
||||||
|
const subtype = urlParams.get(TXO.SUB_TYPE);
|
||||||
|
const active = urlParams.get(TXO.ACTIVE) || TXO.ACTIVE;
|
||||||
|
|
||||||
|
const currentUrlParams = {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
active,
|
||||||
|
type,
|
||||||
|
subtype,
|
||||||
|
};
|
||||||
|
|
||||||
|
// build new json params
|
||||||
|
const params = {};
|
||||||
|
if (currentUrlParams.type) {
|
||||||
|
if (currentUrlParams.type === TXO.SENT) {
|
||||||
|
params[TXO.IS_MY_INPUT] = true;
|
||||||
|
params[TXO.IS_NOT_MY_OUTPUT] = true;
|
||||||
|
if (currentUrlParams.subtype === TXO.TIP) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.SUPPORT;
|
||||||
|
} else if (currentUrlParams.subtype === TXO.PURCHASE) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.PURCHASE;
|
||||||
|
} else if (currentUrlParams.subtype === TXO.PURCHASE) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.OTHER;
|
||||||
|
} else {
|
||||||
|
params[TXO.TX_TYPE] = [TXO.OTHER, TXO.PURCHASE, TXO.SUPPORT];
|
||||||
|
}
|
||||||
|
} else if (currentUrlParams.type === TXO.RECEIVED) {
|
||||||
|
params[TXO.IS_MY_OUTPUT] = true;
|
||||||
|
params[TXO.IS_NOT_MY_INPUT] = true;
|
||||||
|
if (currentUrlParams.subtype === TXO.TIP) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.SUPPORT;
|
||||||
|
} else if (currentUrlParams.subtype === TXO.PURCHASE) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.PURCHASE;
|
||||||
|
} else if (currentUrlParams.subtype === TXO.PURCHASE) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.OTHER;
|
||||||
|
} else {
|
||||||
|
params[TXO.TX_TYPE] = [TXO.OTHER, TXO.PURCHASE, TXO.SUPPORT];
|
||||||
|
}
|
||||||
|
} else if (currentUrlParams.type === TXO.SUPPORT) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.SUPPORT;
|
||||||
|
params[TXO.IS_MY_INPUT] = true;
|
||||||
|
params[TXO.IS_MY_OUTPUT] = true;
|
||||||
|
} else if (currentUrlParams.type === TXO.CHANNEL || currentUrlParams.type === TXO.REPOST) {
|
||||||
|
params[TXO.TX_TYPE] = currentUrlParams.type;
|
||||||
|
} else if (currentUrlParams.type === TXO.PUBLISH) {
|
||||||
|
params[TXO.TX_TYPE] = TXO.STREAM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentUrlParams.active) {
|
||||||
|
if (currentUrlParams.active === 'spent') {
|
||||||
|
params[TXO.IS_SPENT] = true;
|
||||||
|
} else if (currentUrlParams.active === 'active') {
|
||||||
|
params[TXO.IS_NOT_SPENT] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentUrlParams.page) params[TXO.PAGE] = Number(page);
|
||||||
|
if (currentUrlParams.pageSize) params[TXO.PAGE_SIZE] = Number(pageSize);
|
||||||
|
|
||||||
|
function handleChange(delta: Delta) {
|
||||||
|
const url = updateUrl(delta);
|
||||||
|
history.push(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUrl(delta: Delta) {
|
||||||
|
const newUrlParams = new URLSearchParams();
|
||||||
|
switch (delta.dkey) {
|
||||||
|
case TXO.PAGE:
|
||||||
|
if (currentUrlParams.type) {
|
||||||
|
newUrlParams.set(TXO.TYPE, currentUrlParams.type);
|
||||||
|
}
|
||||||
|
if (currentUrlParams.subtype) {
|
||||||
|
newUrlParams.set(TXO.SUB_TYPE, currentUrlParams.subtype);
|
||||||
|
}
|
||||||
|
if (currentUrlParams.active) {
|
||||||
|
newUrlParams.set(TXO.ACTIVE, currentUrlParams.active);
|
||||||
|
}
|
||||||
|
newUrlParams.set(TXO.PAGE, delta.value);
|
||||||
|
break;
|
||||||
|
case TXO.TYPE:
|
||||||
|
newUrlParams.set(TXO.TYPE, delta.value);
|
||||||
|
if (delta.value === TXO.SENT || delta.value === TXO.RECEIVED) {
|
||||||
|
if (currentUrlParams.subtype) {
|
||||||
|
newUrlParams.set(TXO.SUB_TYPE, currentUrlParams.subtype);
|
||||||
|
} else {
|
||||||
|
newUrlParams.set(TXO.SUB_TYPE, 'all');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentUrlParams.active) {
|
||||||
|
newUrlParams.set(TXO.ACTIVE, currentUrlParams.active);
|
||||||
|
}
|
||||||
|
newUrlParams.set(TXO.PAGE, String(1));
|
||||||
|
newUrlParams.set(TXO.PAGE_SIZE, currentUrlParams.pageSize);
|
||||||
|
break;
|
||||||
|
case TXO.SUB_TYPE:
|
||||||
|
if (currentUrlParams.type) {
|
||||||
|
newUrlParams.set(TXO.TYPE, currentUrlParams.type);
|
||||||
|
}
|
||||||
|
if (currentUrlParams.active) {
|
||||||
|
newUrlParams.set(TXO.ACTIVE, currentUrlParams.active);
|
||||||
|
}
|
||||||
|
newUrlParams.set(TXO.SUB_TYPE, delta.value);
|
||||||
|
newUrlParams.set(TXO.PAGE, String(1));
|
||||||
|
newUrlParams.set(TXO.PAGE_SIZE, currentUrlParams.pageSize);
|
||||||
|
break;
|
||||||
|
case TXO.ACTIVE:
|
||||||
|
if (currentUrlParams.type) {
|
||||||
|
newUrlParams.set(TXO.TYPE, currentUrlParams.type);
|
||||||
|
}
|
||||||
|
if (currentUrlParams.subtype) {
|
||||||
|
newUrlParams.set(TXO.SUB_TYPE, currentUrlParams.subtype);
|
||||||
|
}
|
||||||
|
newUrlParams.set(TXO.ACTIVE, delta.value);
|
||||||
|
newUrlParams.set(TXO.PAGE, String(1));
|
||||||
|
newUrlParams.set(TXO.PAGE_SIZE, currentUrlParams.pageSize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return `?${newUrlParams.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsString = JSON.stringify(params);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (paramsString && updateTxoPageParams) {
|
||||||
|
const params = JSON.parse(paramsString);
|
||||||
|
updateTxoPageParams(params);
|
||||||
|
}
|
||||||
|
}, [paramsString, updateTxoPageParams]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={__(`Transactions`)}
|
||||||
|
titleActions={
|
||||||
|
<div className="card__actions--inline">
|
||||||
|
<Button button="secondary" label={__('Refresh')} onClick={() => fetchTxoPage()} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
isBodyTable
|
||||||
|
body={
|
||||||
|
<div>
|
||||||
|
<div className="card__body-actions">
|
||||||
|
<div className="card__actions">
|
||||||
|
{/* show the main drop down */}
|
||||||
|
<div>
|
||||||
|
<FormField
|
||||||
|
type="select"
|
||||||
|
name="type"
|
||||||
|
label={__('Type')}
|
||||||
|
value={type || 'all'}
|
||||||
|
onChange={e => handleChange({ dkey: TXO.TYPE, value: e.target.value })}
|
||||||
|
>
|
||||||
|
{Object.values(TXO.DROPDOWN_TYPES).map(v => {
|
||||||
|
const stringV = String(v);
|
||||||
|
return (
|
||||||
|
<option key={stringV} value={stringV}>
|
||||||
|
{stringV && __(toCapitalCase(stringV))}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</FormField>
|
||||||
|
</div>
|
||||||
|
{/* show the subtypes drop down */}
|
||||||
|
{(type === TXO.SENT || type === TXO.RECEIVED) && (
|
||||||
|
<div>
|
||||||
|
<FormField
|
||||||
|
type="select"
|
||||||
|
name="subtype"
|
||||||
|
label={__('Payment Type')}
|
||||||
|
value={subtype || 'all'}
|
||||||
|
onChange={e => handleChange({ dkey: TXO.SUB_TYPE, value: e.target.value })}
|
||||||
|
>
|
||||||
|
{Object.values(TXO.DROPDOWN_SUBTYPES).map(v => {
|
||||||
|
const stringV = String(v);
|
||||||
|
return (
|
||||||
|
<option key={stringV} value={stringV}>
|
||||||
|
{stringV && __(toCapitalCase(stringV))}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</FormField>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<fieldset-section>
|
||||||
|
<label>Status</label>
|
||||||
|
<div className={'txo__radios'}>
|
||||||
|
<Button
|
||||||
|
button="alt"
|
||||||
|
onClick={e => handleChange({ dkey: TXO.ACTIVE, value: 'active' })}
|
||||||
|
className={classnames(`button-toggle`, {
|
||||||
|
'button-toggle--active': active === TXO.ACTIVE,
|
||||||
|
})}
|
||||||
|
label={__(toCapitalCase('active'))}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
button="alt"
|
||||||
|
onClick={e => handleChange({ dkey: TXO.ACTIVE, value: 'spent' })}
|
||||||
|
className={classnames(`button-toggle`, {
|
||||||
|
'button-toggle--active': active === 'spent',
|
||||||
|
})}
|
||||||
|
label={__(toCapitalCase('historical'))}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
button="alt"
|
||||||
|
onClick={e => handleChange({ dkey: TXO.ACTIVE, value: 'all' })}
|
||||||
|
className={classnames(`button-toggle`, {
|
||||||
|
'button-toggle--active': active === 'all',
|
||||||
|
})}
|
||||||
|
label={__(toCapitalCase('all'))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset-section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* show active/spent */}
|
||||||
|
</div>
|
||||||
|
<TransactionListTable txos={txoPage} />
|
||||||
|
<Paginate totalPages={Math.ceil(txoItemCount / Number(pageSize))} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(TxoList);
|
|
@ -21,7 +21,6 @@ exports.SETTINGS = 'settings';
|
||||||
exports.SHOW = 'show';
|
exports.SHOW = 'show';
|
||||||
exports.ACCOUNT = 'account';
|
exports.ACCOUNT = 'account';
|
||||||
exports.SEARCH = 'search';
|
exports.SEARCH = 'search';
|
||||||
exports.TRANSACTIONS = 'transactions';
|
|
||||||
exports.TAGS_FOLLOWING = 'tags';
|
exports.TAGS_FOLLOWING = 'tags';
|
||||||
exports.DEPRECATED__TAGS_FOLLOWING = 'following/tags';
|
exports.DEPRECATED__TAGS_FOLLOWING = 'following/tags';
|
||||||
exports.TAGS_FOLLOWING_MANAGE = 'tags/manage';
|
exports.TAGS_FOLLOWING_MANAGE = 'tags/manage';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { doAbandonClaim, selectTransactionItems } from 'lbry-redux';
|
import { doAbandonTxo, selectTransactionItems } from 'lbry-redux';
|
||||||
import ModalRevokeClaim from './view';
|
import ModalRevokeClaim from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -9,10 +9,7 @@ const select = state => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
|
abandonTxo: (txo, cb) => dispatch(doAbandonTxo(txo, cb)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(ModalRevokeClaim);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(ModalRevokeClaim);
|
|
||||||
|
|
|
@ -6,15 +6,14 @@ import * as txnTypes from 'constants/transaction_types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
closeModal: () => void,
|
closeModal: () => void,
|
||||||
abandonClaim: (string, number) => void,
|
abandonTxo: (Txo, () => void) => void,
|
||||||
txid: string,
|
tx: Txo,
|
||||||
nout: number,
|
cb: () => void,
|
||||||
transactionItems: Array<Transaction>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ModalRevokeClaim(props: Props) {
|
export default function ModalRevokeClaim(props: Props) {
|
||||||
const { transactionItems, txid, nout, closeModal } = props;
|
const { tx, closeModal, abandonTxo, cb } = props;
|
||||||
const { type, claim_name: name } = transactionItems.find(claim => claim.txid === txid && claim.nout === nout) || {};
|
const { value_type: valueType, type, normalized_name: name } = tx;
|
||||||
const [channelName, setChannelName] = useState('');
|
const [channelName, setChannelName] = useState('');
|
||||||
|
|
||||||
function getButtonLabel(type: string) {
|
function getButtonLabel(type: string) {
|
||||||
|
@ -49,7 +48,11 @@ export default function ModalRevokeClaim(props: Props) {
|
||||||
</p>
|
</p>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
} else if (type === txnTypes.CHANNEL || (type === txnTypes.UPDATE && name.startsWith('@'))) {
|
} else if (
|
||||||
|
valueType === txnTypes.CHANNEL ||
|
||||||
|
type === txnTypes.CHANNEL ||
|
||||||
|
(type === txnTypes.UPDATE && name.startsWith('@'))
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<p>
|
<p>
|
||||||
|
@ -77,10 +80,8 @@ export default function ModalRevokeClaim(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function revokeClaim() {
|
function revokeClaim() {
|
||||||
const { txid, nout } = props;
|
abandonTxo(tx, cb);
|
||||||
|
closeModal();
|
||||||
props.closeModal();
|
|
||||||
props.abandonClaim(txid, nout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -92,7 +93,7 @@ export default function ModalRevokeClaim(props: Props) {
|
||||||
confirmButtonLabel={getButtonLabel(type)}
|
confirmButtonLabel={getButtonLabel(type)}
|
||||||
onConfirmed={revokeClaim}
|
onConfirmed={revokeClaim}
|
||||||
onAborted={closeModal}
|
onAborted={closeModal}
|
||||||
confirmButtonDisabled={type === txnTypes.CHANNEL && name !== channelName}
|
confirmButtonDisabled={valueType === txnTypes.CHANNEL && name !== channelName}
|
||||||
>
|
>
|
||||||
<section>{getMsgBody(type, name)}</section>
|
<section>{getMsgBody(type, name)}</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { doAbandonClaim, selectTransactionItems } from 'lbry-redux';
|
import { selectTransactionItems } from 'lbry-redux';
|
||||||
import ModalSupportsLiquidate from './view';
|
import ModalSupportsLiquidate from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -9,7 +9,6 @@ const select = state => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ModalSupportsLiquidate);
|
export default connect(select, perform)(ModalSupportsLiquidate);
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { doFetchTransactions, makeSelectFilteredTransactionsForPage, selectFilteredTransactionCount } from 'lbry-redux';
|
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
|
|
||||||
import TransactionHistoryPage from './view';
|
|
||||||
|
|
||||||
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()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withRouter(
|
|
||||||
connect(
|
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(TransactionHistoryPage)
|
|
||||||
);
|
|
|
@ -1,37 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import TransactionList from 'component/transactionList';
|
|
||||||
import Page from 'component/page';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
fetchTransactions: () => void,
|
|
||||||
fetchingTransactions: boolean,
|
|
||||||
filteredTransactionPage: Array<{}>,
|
|
||||||
filteredTransactionsCount: number,
|
|
||||||
};
|
|
||||||
|
|
||||||
class TransactionHistoryPage extends React.PureComponent<Props> {
|
|
||||||
componentDidMount() {
|
|
||||||
const { fetchTransactions } = this.props;
|
|
||||||
|
|
||||||
fetchTransactions();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { filteredTransactionPage, filteredTransactionsCount } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Page>
|
|
||||||
<section className="card">
|
|
||||||
<TransactionList
|
|
||||||
transactions={filteredTransactionPage}
|
|
||||||
transactionCount={filteredTransactionsCount}
|
|
||||||
title={__('Transaction History')}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TransactionHistoryPage;
|
|
|
@ -1,13 +1,25 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import WalletBalance from 'component/walletBalance';
|
import WalletBalance from 'component/walletBalance';
|
||||||
import TransactionListRecent from 'component/transactionListRecent';
|
import TxoList from 'component/txoList';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
|
||||||
const WalletPage = () => (
|
type Props = {
|
||||||
|
history: { action: string, push: string => void, replace: string => void },
|
||||||
|
location: { search: string, pathname: string },
|
||||||
|
};
|
||||||
|
|
||||||
|
const WalletPage = (props: Props) => {
|
||||||
|
const { location } = props;
|
||||||
|
const { search } = location;
|
||||||
|
|
||||||
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<WalletBalance />
|
<WalletBalance />
|
||||||
<TransactionListRecent />
|
<TxoList search={search} />
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default WalletPage;
|
export default withRouter(WalletPage);
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
@import 'component/syntax-highlighter';
|
@import 'component/syntax-highlighter';
|
||||||
@import 'component/table';
|
@import 'component/table';
|
||||||
@import 'component/tabs';
|
@import 'component/tabs';
|
||||||
|
@import 'component/txo-list';
|
||||||
@import 'component/tags';
|
@import 'component/tags';
|
||||||
@import 'component/wunderbar';
|
@import 'component/wunderbar';
|
||||||
@import 'component/yrbl';
|
@import 'component/yrbl';
|
||||||
|
|
|
@ -61,6 +61,14 @@
|
||||||
> *:not(:last-child) {
|
> *:not(:last-child) {
|
||||||
margin-right: var(--spacing-medium);
|
margin-right: var(--spacing-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
> * {
|
||||||
|
padding-bottom: var(--spacing-medium);
|
||||||
|
}
|
||||||
|
flex-flow: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__section--flex {
|
.card__section--flex {
|
||||||
|
@ -129,16 +137,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__title {
|
.card__title {
|
||||||
display: flex;
|
display: block;
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: var(--font-title);
|
font-size: var(--font-title);
|
||||||
font-weight: var(--font-weight-light);
|
font-weight: var(--font-weight-light);
|
||||||
|
|
||||||
& > *:not(:last-child) {
|
& > *:not(:last-child) {
|
||||||
margin-right: var(--spacing-medium);
|
margin-right: var(--spacing-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .badge rule inherited from file page prices, should be refactored */
|
/* .badge rule inherited from file page prices, should be refactored */
|
||||||
.badge {
|
.badge {
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -147,6 +152,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__title-actions {
|
||||||
|
padding: var(--spacing-medium);
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card__title.card__title--deprecated {
|
.card__title.card__title--deprecated {
|
||||||
margin-bottom: var(--spacing-small);
|
margin-bottom: var(--spacing-small);
|
||||||
}
|
}
|
||||||
|
@ -187,6 +200,7 @@
|
||||||
|
|
||||||
.card__body {
|
.card__body {
|
||||||
padding: var(--spacing-large);
|
padding: var(--spacing-large);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
&:not(.card__body--no-title) {
|
&:not(.card__body--no-title) {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -207,6 +221,19 @@
|
||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__body-actions {
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
padding: var(--spacing-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__header-actions {
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.card__body--with-icon,
|
.card__body--with-icon,
|
||||||
.card__main-actions--with-icon {
|
.card__main-actions--with-icon {
|
||||||
padding-left: 7.5rem;
|
padding-left: 7.5rem;
|
||||||
|
|
|
@ -38,6 +38,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.table--transactions {
|
.table--transactions {
|
||||||
|
td:nth-of-type(1) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
td:nth-of-type(2) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
td:nth-of-type(3) {
|
td:nth-of-type(3) {
|
||||||
// Only add ellipsis to the links in the table
|
// Only add ellipsis to the links in the table
|
||||||
// We still want to show the entire message if a TX includes one
|
// We still want to show the entire message if a TX includes one
|
||||||
|
@ -47,6 +53,13 @@
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
td:nth-of-type(4) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
td:nth-of-type(5) {
|
||||||
|
width: 15%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
ui/scss/component/_txo-list.scss
Normal file
4
ui/scss/component/_txo-list.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.txo__radios {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
|
@ -6139,9 +6139,9 @@ lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#677dd256437f4cefa3eba96f0a28a814d07736c8:
|
lbry-redux@lbryio/lbry-redux#c51483f4171f0056da65193f979a483248a68297:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/677dd256437f4cefa3eba96f0a28a814d07736c8"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/c51483f4171f0056da65193f979a483248a68297"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue