Merge branch 'tipping_button' of https://github.com/hackrush01/lbry-app into hackrush01-tipping_button

This commit is contained in:
Jeremy Kauffman 2017-09-07 08:20:23 -04:00
commit 0a543a3dcd
26 changed files with 569 additions and 123 deletions

View file

@ -8,11 +8,11 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased]
### Added
* Added a tipping button to send LBRY Credits to the publisher
* File pages now show the time of a publish.
* The "auth token" displayable on Help offers security warning
* Added a new component for rendering dates and times. This component can render the date and time of a block height, as well.
### Changed
*
*

56
ui/js/actions/claims.js Normal file
View file

@ -0,0 +1,56 @@
import lbry from "lbry";
import { selectBalance } from "selectors/wallet";
import { doOpenModal, doShowSnackBar } from "actions/app";
import * as types from "constants/action_types";
import * as modals from "constants/modal_types";
export function doSendSupport(amount, claim_id) {
return function(dispatch, getState) {
const state = getState();
const balance = selectBalance(state);
if (balance - amount <= 0) {
return dispatch(doOpenModal(modals.INSUFFICIENT_BALANCE));
}
dispatch({
type: types.SUPPORT_TRANSACTION_STARTED,
});
const successCallback = results => {
if (results.txid) {
dispatch({
type: types.SUPPORT_TRANSACTION_COMPLETED,
});
dispatch(
doShowSnackBar({
message: __(`You sent ${amount} LBC as support, Mahalo!`),
linkText: __("History"),
linkTarget: __("/wallet"),
})
);
} else {
dispatch({
type: types.SUPPORT_TRANSACTION_FAILED,
data: { error: results },
});
dispatch(doOpenModal(modals.TRANSACTION_FAILED));
}
};
const errorCallback = error => {
dispatch({
type: types.SUPPORT_TRANSACTION_FAILED,
data: { error: error.message },
});
dispatch(doOpenModal(modals.TRANSACTION_FAILED));
};
lbry
.wallet_send({
claim_id: claim_id,
amount: amount,
})
.then(successCallback, errorCallback);
};
}

View file

@ -5,7 +5,8 @@ import {
selectDraftTransactionAmount,
selectBalance,
} from "selectors/wallet";
import { doOpenModal } from "actions/app";
import { doOpenModal, doShowSnackBar } from "actions/app";
import * as modals from "constants/modal_types";
export function doUpdateBalance(balance) {
return {
@ -22,7 +23,7 @@ export function doFetchTransactions() {
type: types.FETCH_TRANSACTIONS_STARTED,
});
lbry.transaction_list().then(results => {
lbry.transaction_list({ include_tip_info: true }).then(results => {
dispatch({
type: types.FETCH_TRANSACTIONS_COMPLETED,
data: {
@ -83,8 +84,8 @@ export function doSendDraftTransaction() {
const balance = selectBalance(state);
const amount = selectDraftTransactionAmount(state);
if (balance - amount < 1) {
return dispatch(doOpenModal("insufficientBalance"));
if (balance - amount <= 0) {
return dispatch(doOpenModal(modals.INSUFFICIENT_BALANCE));
}
dispatch({
@ -96,13 +97,19 @@ export function doSendDraftTransaction() {
dispatch({
type: types.SEND_TRANSACTION_COMPLETED,
});
dispatch(doOpenModal("transactionSuccessful"));
dispatch(
doShowSnackBar({
message: __(`You sent ${amount} LBC`),
linkText: __("History"),
linkTarget: __("/wallet"),
})
);
} else {
dispatch({
type: types.SEND_TRANSACTION_FAILED,
data: { error: results },
});
dispatch(doOpenModal("transactionFailed"));
dispatch(doOpenModal(modals.TRANSACTION_FAILED));
}
};
@ -111,7 +118,7 @@ export function doSendDraftTransaction() {
type: types.SEND_TRANSACTION_FAILED,
data: { error: error.message },
});
dispatch(doOpenModal("transactionFailed"));
dispatch(doOpenModal(modals.TRANSACTION_FAILED));
};
lbry

View file

@ -2,10 +2,7 @@ import React from "react";
import { connect } from "react-redux";
import { selectPageTitle } from "selectors/navigation";
import { selectUser } from "selectors/user";
import {
doCheckUpgradeAvailable,
doAlertError,
} from "actions/app";
import { doCheckUpgradeAvailable, doAlertError } from "actions/app";
import { doRecordScroll } from "actions/navigation";
import { doFetchRewardedContent } from "actions/content";
import { doUpdateBalance } from "actions/wallet";

View file

@ -71,6 +71,7 @@ export class CreditAmount extends React.PureComponent {
showFullPrice: React.PropTypes.bool,
showPlus: React.PropTypes.bool,
look: React.PropTypes.oneOf(["indicator", "plain"]),
fee: React.PropTypes.bool,
};
static defaultProps = {
@ -81,6 +82,7 @@ export class CreditAmount extends React.PureComponent {
showFree: false,
showFullPrice: false,
showPlus: false,
fee: false,
};
render() {
@ -117,7 +119,10 @@ export class CreditAmount extends React.PureComponent {
return (
<span
className={`credit-amount credit-amount--${this.props.look}`}
className={`credit-amount credit-amount--${this.props.look} ${this.props
.fee
? " meta"
: ""}`}
title={fullPrice}
>
<span>

View file

@ -11,7 +11,10 @@ import { makeSelectCostInfoForUri } from "selectors/cost_info";
import { doCloseModal, doOpenModal } from "actions/app";
import { doFetchAvailability } from "actions/availability";
import { doOpenFileInShell, doOpenFileInFolder } from "actions/file_info";
import { makeSelectClaimForUriIsMine } from "selectors/claims";
import {
makeSelectClaimForUriIsMine,
makeSelectClaimForUri,
} from "selectors/claims";
import { doPurchaseUri, doLoadVideo, doStartDownload } from "actions/content";
import FileActions from "./view";
@ -22,6 +25,7 @@ const makeSelect = () => {
const selectCostInfoForUri = makeSelectCostInfoForUri();
const selectLoadingForUri = makeSelectLoadingForUri();
const selectClaimForUriIsMine = makeSelectClaimForUriIsMine();
const selectClaimForUri = makeSelectClaimForUri();
const select = (state, props) => ({
fileInfo: selectFileInfoForUri(state, props),
@ -33,6 +37,7 @@ const makeSelect = () => {
costInfo: selectCostInfoForUri(state, props),
loading: selectLoadingForUri(state, props),
claimIsMine: selectClaimForUriIsMine(state, props),
claimInfo: selectClaimForUri(state, props),
});
return select;

View file

@ -57,6 +57,10 @@ class FileActions extends React.PureComponent {
this.props.loadVideo(this.props.uri);
}
handleSupportButtonClicked() {
this.props.onTipShow();
}
render() {
const {
fileInfo,
@ -73,6 +77,7 @@ class FileActions extends React.PureComponent {
costInfo,
loading,
claimIsMine,
claimInfo,
} = this.props;
const metadata = fileInfo ? fileInfo.metadata : null,
@ -166,6 +171,12 @@ class FileActions extends React.PureComponent {
return (
<section className="file-actions">
{content}
<Link
label={__("Support")}
button="text"
icon="icon-gift"
onClick={this.handleSupportButtonClicked.bind(this)}
/>
{showMenu
? <div className="button-set-item">
<DropDownMenu>

View file

@ -116,7 +116,7 @@ class PublishForm extends React.PureComponent {
? { channel_name: this.state.channel }
: {}),
};
const { source } = this.state;
if (this.refs.file.getValue() !== "") {
@ -262,7 +262,7 @@ class PublishForm extends React.PureComponent {
}
handlePrefillClicked() {
const claimInfo = this.myClaimInfo();
const claimInfo = this.myClaimInfo();
const { source } = claimInfo.value.stream;
const {
license,

View file

@ -0,0 +1,12 @@
import React from "react";
import { connect } from "react-redux";
import { doSendSupport } from "actions/claims";
import TipLink from "./view";
const select = state => ({});
const perform = dispatch => ({
sendSupport: (amount, claim_id) => dispatch(doSendSupport(amount, claim_id)),
});
export default connect(select, perform)(TipLink);

View file

@ -0,0 +1,71 @@
import React from "react";
import Link from "component/link";
import { FormRow } from "component/form";
class TipLink extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
tipAmount: 1.0,
};
}
handleSendButtonClicked() {
let claim_id = this.props.claim_id;
let amount = this.state.tipAmount;
this.props.sendSupport(amount, claim_id);
this.props.onTipHide();
}
handleSupportCancelButtonClicked() {
this.props.onTipHide();
}
handleSupportPriceChange(event) {
this.setState({
tipAmount: Number(event.target.value),
});
}
render() {
return (
<div className="card__content">
<div className="card__title-primary">
<h4>{__("Support")}</h4>
</div>
<div className="card__content">
{__(
"Support the creator and the success of their content by sending a tip. "
)}
<Link label={__("Learn more")} href="https://lbry.io/faq/tipping" />
</div>
<div className="card__content">
<FormRow
label={__("Amount")}
postfix={__("LBC")}
min="0"
step="0.1"
type="number"
placeholder="1.00"
onChange={event => this.handleSupportPriceChange(event)}
/>
</div>
<div className="card__actions">
<Link
label={__("Send")}
button="primary"
onClick={this.handleSendButtonClicked.bind(this)}
/>
<Link
label={__("Cancel")}
button="alt"
onClick={this.handleSupportCancelButtonClicked.bind(this)}
/>
</div>
</div>
);
}
}
export default TipLink;

View file

@ -1,5 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import { doNavigate } from "actions/navigation";
import TransactionList from "./view";
export default connect(null, null)(TransactionList);
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
});
export default connect(null, perform)(TransactionList);

View file

@ -0,0 +1,140 @@
import React from "react";
import LinkTransaction from "component/linkTransaction";
import { CreditAmount } from "component/common";
class TransactionTableBody extends React.PureComponent {
constructor(props) {
super(props);
}
getClaimLink(claim_name, claim_id) {
let uri = `lbry://${claim_name}#${claim_id}`;
return (
<a className="button-text" onClick={() => this.props.navigate(uri)}>
{claim_name}
</a>
);
}
filterList(transaction) {
if (this.props.filter == "claim") {
return transaction.claim_info.length > 0;
} else if (this.props.filter == "support") {
return transaction.support_info.length > 0;
} else if (this.props.filter == "update") {
return transaction.update_info.length > 0;
} else {
return transaction;
}
}
renderBody(transaction) {
const txid = transaction.id;
const date = transaction.date;
const fee = transaction.fee;
const filter = this.props.filter;
const options = {
weekday: "short",
year: "2-digit",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
if (filter == "tipSupport")
transaction["tipSupport_info"] = transaction["support_info"].filter(
tx => tx.is_tip
);
return filter != "unfiltered"
? transaction[`${filter}_info`].map(item => {
return (
<tr key={`${txid}:${item.nout}`}>
<td>
{date
? date.toLocaleDateString("en-US", options)
: <span className="empty">
{__("(Transaction pending)")}
</span>}
</td>
<td>
<CreditAmount
amount={item.amount}
look="plain"
label={false}
showPlus={true}
precision={8}
/>
<br />
<CreditAmount
amount={fee}
look="plain"
fee={true}
label={false}
precision={8}
/>
</td>
<td>
{this.getClaimLink(item.claim_name, item.claim_id)}
</td>
<td>
<LinkTransaction id={txid} />
</td>
</tr>
);
})
: <tr key={txid}>
<td>
{date
? date.toLocaleDateString("en-US", options)
: <span className="empty">
{__("(Transaction pending)")}
</span>}
</td>
<td>
<CreditAmount
amount={transaction.amount}
look="plain"
label={false}
showPlus={true}
precision={8}
/>
<br />
<CreditAmount
amount={fee}
look="plain"
fee={true}
label={false}
precision={8}
/>
</td>
<td>
<LinkTransaction id={txid} />
</td>
</tr>;
}
removeFeeTx(transaction) {
if (this.props.filter == "unfiltered")
return Math.abs(transaction.amount) != Math.abs(transaction.fee);
else return true;
}
render() {
const { transactions, filter } = this.props;
return (
<tbody>
{transactions
.filter(this.filterList, this)
.filter(this.removeFeeTx, this)
.map(this.renderBody, this)}
</tbody>
);
}
}
export default TransactionTableBody;

View file

@ -0,0 +1,19 @@
import React from "react";
class TransactionTableHeader extends React.PureComponent {
render() {
const { filter } = this.props;
return (
<thead>
<tr>
<th>{__("Date")}</th>
<th>{__("Amount(Fee)")}</th>
{filter != "unfiltered" && <th> {__("Claim Name")} </th>}
<th>{__("Transaction")}</th>
</tr>
</thead>
);
}
}
export default TransactionTableHeader;

View file

@ -1,57 +1,65 @@
import React from "react";
import LinkTransaction from "component/linkTransaction";
import { CreditAmount } from "component/common";
import TransactionTableHeader from "./internal/TransactionListHeader";
import TransactionTableBody from "./internal/TransactionListBody";
import FormField from "component/formField";
const TransactionList = props => {
const { emptyMessage, transactions } = props;
class TransactionList extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
filter: "unfiltered",
};
}
handleFilterChanged(event) {
this.setState({
filter: event.target.value,
});
}
handleClaimNameClicked(uri) {
this.props.navigate("/show", { uri });
}
render() {
const { emptyMessage, transactions } = this.props;
const { filter } = this.state;
if (!transactions || !transactions.length) {
return (
<div className="empty">
{emptyMessage || __("No transactions to list.")}
</div>
);
}
if (!transactions || !transactions.length) {
return (
<div className="empty">
{emptyMessage || __("No transactions to list.")}
<div>
<span className="sort-section">
{__("Filter")} {" "}
<FormField
type="select"
onChange={this.handleFilterChanged.bind(this)}
>
<option value="unfiltered">{__("All")}</option>
<option value="claim">{__("Publishes")}</option>
<option value="support">{__("Supports")}</option>
<option value="tipSupport">{__("Tips")}</option>
<option value="update">{__("Updates")}</option>
</FormField>
</span>
<table className="table-standard table-stretch">
<TransactionTableHeader filter={filter} />
<TransactionTableBody
transactions={transactions}
filter={filter}
navigate={this.handleClaimNameClicked.bind(this)}
/>
</table>
</div>
);
}
return (
<table className="table-standard table-stretch">
<thead>
<tr>
<th>{__("Date")}</th>
<th>{__("Amount")}</th>
<th>{__("Transaction")}</th>
</tr>
</thead>
<tbody>
{transactions.map(item => {
return (
<tr key={item.id}>
<td>
{item.date
? item.date.toLocaleDateString() +
" " +
item.date.toLocaleTimeString()
: <span className="empty">
{__("(Transaction pending)")}
</span>}
</td>
<td>
<CreditAmount
amount={item.amount}
look="plain"
showPlus={true}
precision={8}
/>{" "}
</td>
<td>
<LinkTransaction id={item.id} />
</td>
</tr>
);
})}
</tbody>
</table>
);
};
}
export default TransactionList;

View file

@ -1,12 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal } from "actions/app";
import {
doSendDraftTransaction,
doSetDraftTransactionAmount,
doSetDraftTransactionAddress,
} from "actions/wallet";
import { selectCurrentModal } from "selectors/app";
import {
selectDraftTransactionAmount,
selectDraftTransactionAddress,
@ -16,14 +14,12 @@ import {
import WalletSend from "./view";
const select = state => ({
modal: selectCurrentModal(state),
address: selectDraftTransactionAddress(state),
amount: selectDraftTransactionAmount(state),
error: selectDraftTransactionError(state),
});
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
sendToAddress: () => dispatch(doSendDraftTransaction()),
setAmount: event => dispatch(doSetDraftTransactionAmount(event.target.value)),
setAddress: event =>

View file

@ -1,20 +1,10 @@
import React from "react";
import Link from "component/link";
import Modal from "modal/modal";
import { FormRow } from "component/form";
import lbryuri from "lbryuri";
const WalletSend = props => {
const {
sendToAddress,
closeModal,
modal,
setAmount,
setAddress,
amount,
address,
error,
} = props;
const { sendToAddress, setAmount, setAddress, amount, address } = props;
return (
<section className="card">
@ -57,32 +47,6 @@ const WalletSend = props => {
</div>
</div>
</form>
{modal == "insufficientBalance" &&
<Modal
isOpen={true}
contentLabel={__("Insufficient balance")}
onConfirmed={closeModal}
>
{__(
"Insufficient balance: after this transaction you would have less than 1 LBC in your wallet."
)}
</Modal>}
{modal == "transactionSuccessful" &&
<Modal
isOpen={true}
contentLabel={__("Transaction successful")}
onConfirmed={closeModal}
>
{__("Your transaction was successfully placed in the queue.")}
</Modal>}
{modal == "transactionFailed" &&
<Modal
isOpen={true}
contentLabel={__("Transaction failed")}
onConfirmed={closeModal}
>
{error}
</Modal>}
</section>
);
};

View file

@ -132,6 +132,11 @@ export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE";
export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR";
export const FETCH_REWARD_CONTENT_COMPLETED = "FETCH_REWARD_CONTENT_COMPLETED";
// Supports
export const SUPPORT_TRANSACTION_STARTED = "SUPPORT_TRANSACTION_STARTED";
export const SUPPORT_TRANSACTION_COMPLETED = "SUPPORT_TRANSACTION_COMPLETED";
export const SUPPORT_TRANSACTION_FAILED = "SUPPORT_TRANSACTION_FAILED";
//Language
export const DOWNLOAD_LANGUAGE_SUCCEEDED = "DOWNLOAD_LANGUAGE_SUCCEEDED";
export const DOWNLOAD_LANGUAGE_FAILED = "DOWNLOAD_LANGUAGE_FAILED";

View file

@ -7,5 +7,7 @@ export const UPGRADE = "upgrade";
export const WELCOME = "welcome";
export const FIRST_REWARD = "first_reward";
export const AUTHENTICATION_FAILURE = "auth_failure";
export const TRANSACTION_FAILED = "transaction_failed";
export const INSUFFICIENT_BALANCE = "insufficient_balance";
export const REWARD_APPROVAL_REQUIRED = "reward_approval_required";
export const CREDIT_INTRO = "credit_intro";

View file

@ -0,0 +1,16 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal, doNavigate } from "actions/app";
import ModalInsufficientBalance from "./view";
const select = state => ({});
const perform = dispatch => ({
addBalance: () => {
dispatch(doNavigate("/wallet"));
dispatch(doCloseModal());
},
closeModal: () => dispatch(doCloseModal()),
});
export default connect(select, perform)(ModalInsufficientBalance);

View file

@ -0,0 +1,26 @@
import React from "react";
import { Modal } from "modal/modal";
class ModalInsufficientBalance extends React.PureComponent {
render() {
const { addBalance, closeModal } = this.props;
return (
<Modal
isOpen={true}
type="confirm"
contentLabel={__("Not enough credits")}
confirmButtonLabel={__("Get Credits")}
abortButtonLabel={__("Cancel")}
onAborted={closeModal}
onConfirmed={addBalance}
>
{__(
"Insufficient balance: after this transaction you would have less than 0 LBCs in your wallet."
)}
</Modal>
);
}
}
export default ModalInsufficientBalance;

View file

@ -7,8 +7,10 @@ import ModalUpgrade from "modal/modalUpgrade";
import ModalWelcome from "modal/modalWelcome";
import ModalFirstReward from "modal/modalFirstReward";
import ModalRewardApprovalRequired from "modal/modalRewardApprovalRequired";
import * as modals from "constants/modal_types";
import ModalCreditIntro from "modal/modalCreditIntro";
import ModalTransactionFailed from "modal/modalTransactionFailed";
import ModalInsufficientBalance from "modal/modalInsufficientBalance";
import * as modals from "constants/modal_types";
class ModalRouter extends React.PureComponent {
constructor(props) {
@ -115,6 +117,10 @@ class ModalRouter extends React.PureComponent {
return <ModalAuthFailure />;
case modals.CREDIT_INTRO:
return <ModalCreditIntro />;
case modals.TRANSACTION_FAILED:
return <ModalTransactionFailed />;
case modals.INSUFFICIENT_BALANCE:
return <ModalInsufficientBalance />;
case modals.REWARD_APPROVAL_REQUIRED:
return <ModalRewardApprovalRequired />;
default:

View file

@ -0,0 +1,12 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal } from "actions/app";
import ModalTransactionFailed from "./view";
const select = state => ({});
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
});
export default connect(select, perform)(ModalTransactionFailed);

View file

@ -0,0 +1,20 @@
import React from "react";
import { Modal } from "modal/modal";
class ModalTransactionFailed extends React.PureComponent {
render() {
const { closeModal } = this.props;
return (
<Modal
isOpen={true}
contentLabel={__("Transaction failed")}
onConfirmed={closeModal}
>
{__("Something went wrong")}:
</Modal>
);
}
}
export default ModalTransactionFailed;

View file

@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown";
import lbry from "lbry.js";
import lbryuri from "lbryuri.js";
import Video from "component/video";
import TipLink from "component/tipLink";
import { Thumbnail } from "component/common";
import FilePrice from "component/filePrice";
import FileActions from "component/fileActions";
@ -42,6 +43,13 @@ const FormatItem = props => {
};
class FilePage extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
showTipBox: false,
};
}
componentDidMount() {
this.fetchFileInfo(this.props);
this.fetchCostInfo(this.props);
@ -63,6 +71,18 @@ class FilePage extends React.PureComponent {
}
}
handleTipShow() {
this.setState({
showTipBox: true,
});
}
handleTipHide() {
this.setState({
showTipBox: false,
});
}
render() {
const {
claim,
@ -73,6 +93,8 @@ class FilePage extends React.PureComponent {
rewardedContentClaimIds,
} = this.props;
const { showTipBox } = this.state;
if (!claim || !metadata) {
return (
<span className="empty">{__("Empty claim or metadata info.")}</span>
@ -135,18 +157,22 @@ class FilePage extends React.PureComponent {
: uriIndicator}
</div>
<div className="card__actions">
<FileActions uri={uri} />
<FileActions
uri={uri}
onTipShow={this.handleTipShow.bind(this)}
/>
</div>
</div>
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
<ReactMarkdown
source={(metadata && metadata.description) || ""}
escapeHtml={true}
disallowedTypes={["Heading", "HtmlInline", "HtmlBlock"]}
/>
</div>
{!showTipBox &&
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
<ReactMarkdown
source={(metadata && metadata.description) || ""}
escapeHtml={true}
disallowedTypes={["Heading", "HtmlInline", "HtmlBlock"]}
/>
</div>}
</div>
{metadata && claim
{metadata && claim && !showTipBox
? <div className="card__content">
<FormatItem
metadata={metadata}
@ -155,13 +181,21 @@ class FilePage extends React.PureComponent {
/>
</div>
: ""}
<div className="card__content">
<Link
href={`https://lbry.io/dmca?claim_id=${claim.claim_id}`}
label={__("report")}
className="button-text-help"
/>
</div>
{showTipBox
? <TipLink
onTipShow={this.handleTipShow.bind(this)}
onTipHide={this.handleTipHide.bind(this)}
claim_id={claim.claim_id}
/>
: ""}
{!showTipBox &&
<div className="card__content">
<Link
href={`https://lbry.io/dmca?claim_id=${claim.claim_id}`}
label={__("report")}
className="button-text-help"
/>
</div>}
</section>
</main>
);

View file

@ -189,6 +189,31 @@ reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) {
});
};
reducers[types.SUPPORT_TRANSACTION_STARTED] = function(state, action) {
const newSupportTransaction = Object.assign({}, state.supportTransaction, {
sendingSupport: true,
});
return Object.assign({}, state, {
supportTransaction: newSupportTransaction,
});
};
reducers[types.SUPPORT_TRANSACTION_COMPLETED] = function(state, action) {
return Object.assign({}, state);
};
reducers[types.SUPPORT_TRANSACTION_FAILED] = function(state, action) {
const newSupportTransaction = Object.assign({}, state.supportTransaction, {
sendingSupport: false,
error: action.data.error,
});
return Object.assign({}, state, {
supportTransaction: newSupportTransaction,
});
};
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);

View file

@ -28,6 +28,10 @@ export const selectTransactionItems = createSelector(
id: txid,
date: tx.timestamp ? new Date(parseInt(tx.timestamp) * 1000) : null,
amount: parseFloat(tx.value),
claim_info: tx.claim_info,
support_info: tx.support_info,
update_info: tx.update_info,
fee: tx.fee,
});
});
return transactionItems.reverse();