diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 12028b6de..25a3bf8bb 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -131,6 +131,7 @@ export function CommentCreate(props: Props) { return; } + // if comment post didn't work, but tip was already made, try againt o create comment if (commentFailure && tipAmount === successTip.tipAmount) { handleCreateComment(successTip.txid); return; @@ -147,6 +148,19 @@ export function CommentCreate(props: Props) { const activeChannelName = activeChannelClaim && activeChannelClaim.name; const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id; + // setup variables for tip API + let channelClaimId, tipChannelName; + // if there is a signing channel it's on a file + if (claim.signing_channel) { + channelClaimId = claim.signing_channel.claim_id; + tipChannelName = claim.signing_channel.name; + + // otherwise it's on the channel page + } else { + channelClaimId = claim.claim_id; + tipChannelName = claim.name; + } + console.log(activeChannelClaim); setIsSubmitting(true); @@ -162,6 +176,14 @@ export function CommentCreate(props: Props) { setTimeout(() => { handleCreateComment(txid); }, 1500); + + doToast({ + message: __("You sent %tipAmount% LBRY Credits as a tip to %tipChannelName%, I'm sure they appreciate it!", { + tipAmount: tipAmount, // force show decimal places + tipChannelName + }), + }); + setSuccessTip({ txid, tipAmount }); }, () => { @@ -170,18 +192,6 @@ export function CommentCreate(props: Props) { } ); } else { - // setup variables for tip API - let channelClaimId, tipChannelName; - // if there is a signing channel it's on a file - if (claim.signing_channel) { - channelClaimId = claim.signing_channel.claim_id; - tipChannelName = claim.signing_channel.name; - - // otherwise it's on the channel page - } else { - channelClaimId = claim.claim_id; - tipChannelName = claim.name; - } const sourceClaimId = claim.claim_id; @@ -190,8 +200,8 @@ export function CommentCreate(props: Props) { Lbryio.call( 'customer', 'tip', - { - amount: 100 * roundedAmount, // convert from dollars to cents + { // round to deal with floating point precision + amount: Math.round(100 * roundedAmount), // convert from dollars to cents creator_channel_name: tipChannelName, // creator_channel_name creator_channel_claim_id: channelClaimId, tipper_channel_name: activeChannelName, diff --git a/ui/component/livestreamComments/view.jsx b/ui/component/livestreamComments/view.jsx index f4b073fef..0ed35c9e3 100644 --- a/ui/component/livestreamComments/view.jsx +++ b/ui/component/livestreamComments/view.jsx @@ -22,7 +22,9 @@ type Props = { fetchingComments: boolean, doSuperChatList: (string) => void, superChats: Array, + superChatsReversed: Array, superChatsTotalAmount: number, + superChatsFiatAmount: number, myChannels: ?Array, }; @@ -38,15 +40,16 @@ export default function LivestreamComments(props: Props) { embed, doCommentSocketConnect, doCommentSocketDisconnect, - comments, + comments, // superchats in chronological format doCommentList, fetchingComments, doSuperChatList, - superChats, - superChatsTotalAmount, myChannels, + superChats, // superchats organized by tip amount } = props; + let { superChatsReversed, superChatsFiatAmount, superChatsTotalAmount } = props; + const commentsRef = React.createRef(); const [scrollBottom, setScrollBottom] = React.useState(true); const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT); @@ -58,6 +61,36 @@ export default function LivestreamComments(props: Props) { const discussionElement = document.querySelector('.livestream__comments'); const commentElement = document.querySelector('.livestream-comment'); + // sum total amounts for fiat tips and lbc tips + if (superChats) { + let fiatAmount = 0; + let LBCAmount = 0; + for (const superChat of superChats) { + if (superChat.is_fiat) { + fiatAmount = fiatAmount + superChat.support_amount; + } else { + LBCAmount = LBCAmount + superChat.support_amount; + } + } + + superChatsFiatAmount = fiatAmount; + superChatsTotalAmount = LBCAmount; + } + + // array of superchats organized by fiat or not first, then support amount + if (superChats) { + const clonedSuperchats = JSON.parse(JSON.stringify(superChats)); + + // sort by fiat first then by support amount + superChatsReversed = clonedSuperchats.sort(function(a,b) { + if (a.is_fiat === b.is_fiat) { + return b.support_amount - a.support_amount; + } else { + return (a.is_fiat === b.is_fiat) ? 0 : a.is_fiat ? -1 : 1; + } + }).reverse(); + } + // todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine function isMyComment(channelId: string) { if (myChannels != null && channelId != null) { @@ -71,6 +104,7 @@ export default function LivestreamComments(props: Props) { } React.useEffect(() => { + if (claimId) { doCommentList(uri, '', 1, 75); doSuperChatList(uri); @@ -132,24 +166,38 @@ export default function LivestreamComments(props: Props) {
{__('Live discussion')}
{superChatsTotalAmount > 0 && (
+ + {/* the superchats in chronological order button */}
)} @@ -187,9 +235,23 @@ export default function LivestreamComments(props: Props) { )} + {/* top to bottom comment display */} {!fetchingComments && comments.length > 0 ? (
- {commentsToDisplay.map((comment) => ( + {viewMode === VIEW_MODE_CHAT && commentsToDisplay.map((comment) => ( + + ))} + + {viewMode === VIEW_MODE_SUPER_CHAT && superChatsReversed && superChatsReversed.map((comment) => ( ({ + balance: selectBalance(state), + claimsBalance: selectClaimsBalance(state) || 0, + supportsBalance: selectSupportsBalance(state) || 0, + tipsBalance: selectTipsBalance(state) || 0, + rewards: selectClaimedRewards(state), + hasSynced: Boolean(selectSyncHash(state)), + fetchingUtxoCounts: selectIsFetchingUtxoCounts(state), + consolidatingUtxos: selectIsConsolidatingUtxos(state), + massClaimingTips: selectIsMassClaimingTips(state), + utxoCounts: selectUtxoCounts(state), + consolidateIsPending: selectPendingConsolidateTxid(state), + massClaimIsPending: selectPendingMassClaimTxid(state), +}); + +export default connect(select, { + doOpenModal, + doFetchUtxoCounts, + doUtxoConsolidate, +})(WalletBalance); diff --git a/ui/component/walletFiatAccountHistory/view.jsx b/ui/component/walletFiatAccountHistory/view.jsx new file mode 100644 index 000000000..3aba2b5d8 --- /dev/null +++ b/ui/component/walletFiatAccountHistory/view.jsx @@ -0,0 +1,181 @@ +// @flow +import * as ICONS from 'constants/icons'; +import * as MODALS from 'constants/modal_types'; +import * as PAGES from 'constants/pages'; +import React from 'react'; +import CreditAmount from 'component/common/credit-amount'; +import Button from 'component/button'; +import HelpLink from 'component/common/help-link'; +import Card from 'component/common/card'; +import Icon from 'component/common/icon'; +import LbcSymbol from 'component/common/lbc-symbol'; +import I18nMessage from 'component/i18nMessage'; +import { formatNumberWithCommas } from 'util/number'; +import { Lbryio } from 'lbryinc'; +import moment from 'moment'; + +type Props = { + accountDetails: any, + transactions: any, +}; + +const WalletBalance = (props: Props) => { + const { + + } = props; + + // receive transactions from parent component + let accountTransactions = props.transactions; + + // reverse so most recent payments come first + if(accountTransactions){ + accountTransactions = accountTransactions.reverse(); + } + + if(accountTransactions && accountTransactions.length > 10 ){ + accountTransactions.length = 10; + } + + const [detailsExpanded, setDetailsExpanded] = React.useState(false); + const [accountStatusResponse, setAccountStatusResponse] = React.useState(); + const [subscriptions, setSubscriptions] = React.useState([]); + + var environment = 'test'; + + function getAccountStatus(){ + return Lbryio.call( + 'account', + 'status', + { + environment + }, + 'post' + ); + } + + React.useEffect(() => { + + + (async function(){ + const response = await getAccountStatus(); + + setAccountStatusResponse(response); + + console.log(response); + })(); + }, []); + + return ( + <> +
+ + + + + + + + + + + + + + {accountTransactions && + accountTransactions.map((transaction) => ( + + + + + + + + + + ))} + +
{__('Date')}{<>{__('Receiving Channel Name')}}{__('Tip Location')}{__('Amount (USD)')} {__('Processing Fee')}{__('Odysee Fee')}{__('Received Amount')}
{moment(transaction.created_at).format('LLL')} + + ${transaction.tipped_amount / 100}${transaction.transaction_fee / 100}${transaction.application_fee / 100}${transaction.received_amount / 100}
+ {!accountTransactions &&

No Transactions

} +
+ + )} + /> + + +
+ + + + + + + + + + + + + {subscriptions && + subscriptions.reverse().map((transaction) => ( + + + + + + + + + ))} + +
{__('Date')}{<>{__('Receiving Channel Name')}}{__('Tip Location')}{__('Amount (USD)')} {__('Card Last 4')}{__('Anonymous')}
{moment(transaction.created_at).format('LLL')} + + ${transaction.tipped_amount / 100}{lastFour}{transaction.private_tip ? 'Yes' : 'No'}
+ {(!subscriptions || subscriptions.length === 0) &&

No Subscriptions

} +
+ + } + /> + ); +}; + +export default WalletBalance; diff --git a/ui/component/walletFiatBalance/index.js b/ui/component/walletFiatBalance/index.js new file mode 100644 index 000000000..83b0a30cf --- /dev/null +++ b/ui/component/walletFiatBalance/index.js @@ -0,0 +1,40 @@ +import { connect } from 'react-redux'; +import { + selectBalance, + selectClaimsBalance, + selectSupportsBalance, + selectTipsBalance, + selectIsFetchingUtxoCounts, + selectUtxoCounts, + doFetchUtxoCounts, + doUtxoConsolidate, + selectIsConsolidatingUtxos, + selectIsMassClaimingTips, + selectPendingConsolidateTxid, + selectPendingMassClaimTxid, +} from 'lbry-redux'; +import { doOpenModal } from 'redux/actions/app'; +import { selectSyncHash } from 'redux/selectors/sync'; +import { selectClaimedRewards } from 'redux/selectors/rewards'; +import WalletBalance from './view'; + +const select = state => ({ + balance: selectBalance(state), + claimsBalance: selectClaimsBalance(state) || 0, + supportsBalance: selectSupportsBalance(state) || 0, + tipsBalance: selectTipsBalance(state) || 0, + rewards: selectClaimedRewards(state), + hasSynced: Boolean(selectSyncHash(state)), + fetchingUtxoCounts: selectIsFetchingUtxoCounts(state), + consolidatingUtxos: selectIsConsolidatingUtxos(state), + massClaimingTips: selectIsMassClaimingTips(state), + utxoCounts: selectUtxoCounts(state), + consolidateIsPending: selectPendingConsolidateTxid(state), + massClaimIsPending: selectPendingMassClaimTxid(state), +}); + +export default connect(select, { + doOpenModal, + doFetchUtxoCounts, + doUtxoConsolidate, +})(WalletBalance); diff --git a/ui/component/walletFiatBalance/view.jsx b/ui/component/walletFiatBalance/view.jsx new file mode 100644 index 000000000..fbf08ba55 --- /dev/null +++ b/ui/component/walletFiatBalance/view.jsx @@ -0,0 +1,103 @@ +// @flow +import * as ICONS from 'constants/icons'; +import * as MODALS from 'constants/modal_types'; +import * as PAGES from 'constants/pages'; +import React from 'react'; +import CreditAmount from 'component/common/credit-amount'; +import Button from 'component/button'; +import HelpLink from 'component/common/help-link'; +import Card from 'component/common/card'; +import Icon from 'component/common/icon'; +import LbcSymbol from 'component/common/lbc-symbol'; +import I18nMessage from 'component/i18nMessage'; +import { formatNumberWithCommas } from 'util/number'; + +type Props = { + accountDetails: any, +}; + +const WalletBalance = (props: Props) => { + const { + accountDetails, + } = props; + + console.log('account details'); + console.log(accountDetails); + + const [detailsExpanded, setDetailsExpanded] = React.useState(false); + + return ( + <>{1 == 1 && {accountDetails && accountDetails.total_received_unpaid/100 || 0} USD} + subtitle={ + + This is your remaining balance that can still be withdrawn to your bank account + + } + actions={ + <> +

+ ${accountDetails && accountDetails.total_tipped / 100 || 0} Total Received Tips +

+ +

+ ${accountDetails && accountDetails.total_paid_out/100 || 0} Withdrawn +

+ + {/* view more section */} + {detailsExpanded && ( +
+
+
+ {__('Earned from uploads')} + {/*({__('Earned from channel page')})*/} +
+
+ + {Boolean(1) && ( +
+ +
+ {__('Earned from channel page')} + {/*({__('Delete or edit past content to spend')})*/} +
+
+ +
+ + {/*
*/} + {/* {__('...supporting content')}*/} + {/* ({__('Delete supports to spend')})*/} + {/*
*/} + {/*
*/} + {/* */} + {/*
*/} +
+
+ )} + +
+
+ + } + />} + ); +}; + +export default WalletBalance; diff --git a/ui/component/walletFiatPaymentBalance/index.js b/ui/component/walletFiatPaymentBalance/index.js new file mode 100644 index 000000000..83b0a30cf --- /dev/null +++ b/ui/component/walletFiatPaymentBalance/index.js @@ -0,0 +1,40 @@ +import { connect } from 'react-redux'; +import { + selectBalance, + selectClaimsBalance, + selectSupportsBalance, + selectTipsBalance, + selectIsFetchingUtxoCounts, + selectUtxoCounts, + doFetchUtxoCounts, + doUtxoConsolidate, + selectIsConsolidatingUtxos, + selectIsMassClaimingTips, + selectPendingConsolidateTxid, + selectPendingMassClaimTxid, +} from 'lbry-redux'; +import { doOpenModal } from 'redux/actions/app'; +import { selectSyncHash } from 'redux/selectors/sync'; +import { selectClaimedRewards } from 'redux/selectors/rewards'; +import WalletBalance from './view'; + +const select = state => ({ + balance: selectBalance(state), + claimsBalance: selectClaimsBalance(state) || 0, + supportsBalance: selectSupportsBalance(state) || 0, + tipsBalance: selectTipsBalance(state) || 0, + rewards: selectClaimedRewards(state), + hasSynced: Boolean(selectSyncHash(state)), + fetchingUtxoCounts: selectIsFetchingUtxoCounts(state), + consolidatingUtxos: selectIsConsolidatingUtxos(state), + massClaimingTips: selectIsMassClaimingTips(state), + utxoCounts: selectUtxoCounts(state), + consolidateIsPending: selectPendingConsolidateTxid(state), + massClaimIsPending: selectPendingMassClaimTxid(state), +}); + +export default connect(select, { + doOpenModal, + doFetchUtxoCounts, + doUtxoConsolidate, +})(WalletBalance); diff --git a/ui/component/walletFiatPaymentBalance/view.jsx b/ui/component/walletFiatPaymentBalance/view.jsx new file mode 100644 index 000000000..669806d08 --- /dev/null +++ b/ui/component/walletFiatPaymentBalance/view.jsx @@ -0,0 +1,75 @@ +// @flow +import * as ICONS from 'constants/icons'; +import * as MODALS from 'constants/modal_types'; +import * as PAGES from 'constants/pages'; +import React from 'react'; +import CreditAmount from 'component/common/credit-amount'; +import Button from 'component/button'; +import HelpLink from 'component/common/help-link'; +import Card from 'component/common/card'; +import Icon from 'component/common/icon'; +import LbcSymbol from 'component/common/lbc-symbol'; +import I18nMessage from 'component/i18nMessage'; +import { formatNumberWithCommas } from 'util/number'; + +type Props = { + totalTippedAmount: number, + accountDetails: any, + transactions: any, +}; + + + +const WalletBalance = (props: Props) => { + const { + accountDetails, + totalTippedAmount, + transactions, + } = props; + + const [detailsExpanded, setDetailsExpanded] = React.useState(false); + const [totalCreatorsSupported, setTotalCreatorsSupported] = React.useState(false); + + // calculate how many unique users tipped + React.useEffect(() => { + if(transactions){ + let channelNames = [] + + for(const transaction of transactions){ + channelNames.push(transaction.channel_name) + console.log(transaction.channel_name); + } + + let unique = [...new Set(channelNames)]; + setTotalCreatorsSupported(unique.length); + } + }, [transactions]); + + return ( + <>{1 == 1 && {totalTippedAmount} USD} + subtitle={ + + The total amount you have tipped to different creators + + } + actions={ + <> +

+ {transactions && transactions.length} Total Tips +

+ +

+ {totalCreatorsSupported || 0} Creators Supported +

+ +
+
+ + } + />} + ); +}; + +export default WalletBalance; diff --git a/ui/component/walletFiatPaymentHistory/index.js b/ui/component/walletFiatPaymentHistory/index.js new file mode 100644 index 000000000..83b0a30cf --- /dev/null +++ b/ui/component/walletFiatPaymentHistory/index.js @@ -0,0 +1,40 @@ +import { connect } from 'react-redux'; +import { + selectBalance, + selectClaimsBalance, + selectSupportsBalance, + selectTipsBalance, + selectIsFetchingUtxoCounts, + selectUtxoCounts, + doFetchUtxoCounts, + doUtxoConsolidate, + selectIsConsolidatingUtxos, + selectIsMassClaimingTips, + selectPendingConsolidateTxid, + selectPendingMassClaimTxid, +} from 'lbry-redux'; +import { doOpenModal } from 'redux/actions/app'; +import { selectSyncHash } from 'redux/selectors/sync'; +import { selectClaimedRewards } from 'redux/selectors/rewards'; +import WalletBalance from './view'; + +const select = state => ({ + balance: selectBalance(state), + claimsBalance: selectClaimsBalance(state) || 0, + supportsBalance: selectSupportsBalance(state) || 0, + tipsBalance: selectTipsBalance(state) || 0, + rewards: selectClaimedRewards(state), + hasSynced: Boolean(selectSyncHash(state)), + fetchingUtxoCounts: selectIsFetchingUtxoCounts(state), + consolidatingUtxos: selectIsConsolidatingUtxos(state), + massClaimingTips: selectIsMassClaimingTips(state), + utxoCounts: selectUtxoCounts(state), + consolidateIsPending: selectPendingConsolidateTxid(state), + massClaimIsPending: selectPendingMassClaimTxid(state), +}); + +export default connect(select, { + doOpenModal, + doFetchUtxoCounts, + doUtxoConsolidate, +})(WalletBalance); diff --git a/ui/component/walletFiatPaymentHistory/view.jsx b/ui/component/walletFiatPaymentHistory/view.jsx new file mode 100644 index 000000000..a9c9f9f83 --- /dev/null +++ b/ui/component/walletFiatPaymentHistory/view.jsx @@ -0,0 +1,219 @@ +// @flow +import * as ICONS from 'constants/icons'; +import * as MODALS from 'constants/modal_types'; +import * as PAGES from 'constants/pages'; +import React from 'react'; +import CreditAmount from 'component/common/credit-amount'; +import Button from 'component/button'; +import HelpLink from 'component/common/help-link'; +import Card from 'component/common/card'; +import Icon from 'component/common/icon'; +import LbcSymbol from 'component/common/lbc-symbol'; +import I18nMessage from 'component/i18nMessage'; +import { formatNumberWithCommas } from 'util/number'; +import { Lbryio } from 'lbryinc'; +import moment from 'moment'; + +type Props = { + accountDetails: any, + transactions: any, + totalTippedAmount: number, +}; + +const WalletBalance = (props: Props) => { + const { + + } = props; + + // receive transactions from parent component + let accountTransactions = props.transactions; + + console.log('heres transactions') + console.log(accountTransactions); + + // let totalTippedAmount = props.totalTippedAmount; + + // totalTippedAmount = 0; + + + + // reverse so most recent payments come first + if(accountTransactions){ + accountTransactions = accountTransactions.reverse(); + } + + const [detailsExpanded, setDetailsExpanded] = React.useState(false); + const [accountStatusResponse, setAccountStatusResponse] = React.useState(); + const [paymentHistoryTransactions, setPaymentHistoryTransactions] = React.useState(); + const [subscriptions, setSubscriptions] = React.useState(); + const [totalTippedAmount, setTotalTippedAmount] = React.useState(0); + + + const [lastFour, setLastFour] = React.useState(); + + var environment = 'test'; + + function getPaymentHistory() { + return Lbryio.call( + 'customer', + 'list', + { + environment, + }, + 'post' + )}; + + function getCustomerStatus(){ + return Lbryio.call( + 'customer', + 'status', + { + environment, + }, + 'post' + ) + } + + React.useEffect(() => { + (async function(){ + let response = accountTransactions; + + console.log('payment transactions'); + console.log(response); + + const customerStatusResponse = await getCustomerStatus(); + + setLastFour(customerStatusResponse.PaymentMethods[0].card.last4); + + if (response && response.length > 10) response.length = 10; + + setPaymentHistoryTransactions(response); + + const subscriptions = [...response]; + + if(subscriptions && subscriptions.length > 2){ + subscriptions.length = 2 + setSubscriptions([]) + } else { + setSubscriptions([]) + } + + console.log(response); + + })(); + }, [accountTransactions]); + + return ( + <> + +
+ + + + + + + + + + + + + {accountTransactions && + accountTransactions.map((transaction) => ( + + + + + + + + + ))} + +
{__('Date')}{<>{__('Receiving Channel Name')}}{__('Tip Location')}{__('Amount (USD)')} {__('Card Last 4')}{__('Anonymous')}
{moment(transaction.created_at).format('LLL')} + + ${transaction.tipped_amount / 100}{lastFour}{transaction.private_tip ? 'Yes' : 'No'}
+ {(!accountTransactions || accountTransactions.length === 0) &&

No Transactions

} +
+ + } + /> + + +
+ + + + + + + + + + + + + {subscriptions && + subscriptions.reverse().map((transaction) => ( + + + + + + + + + ))} + +
{__('Date')}{<>{__('Receiving Channel Name')}}{__('Tip Location')}{__('Amount (USD)')} {__('Card Last 4')}{__('Anonymous')}
{moment(transaction.created_at).format('LLL')} + + ${transaction.tipped_amount / 100}{lastFour}{transaction.private_tip ? 'Yes' : 'No'}
+ {(!subscriptions || subscriptions.length === 0) &&

No Subscriptions

} +
+ + } + /> + + ); +}; + +export default WalletBalance; diff --git a/ui/component/walletSendTip/view.jsx b/ui/component/walletSendTip/view.jsx index 981833ffe..76f75851d 100644 --- a/ui/component/walletSendTip/view.jsx +++ b/ui/component/walletSendTip/view.jsx @@ -25,6 +25,8 @@ if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) { } const DEFAULT_TIP_AMOUNTS = [1, 5, 25, 100]; +const MINIMUM_FIAT_TIP = 1; +const MAXIMUM_FIAT_TIP = 1000; const TAB_BOOST = 'TabBoost'; const TAB_FIAT = 'TabFiat'; @@ -186,8 +188,7 @@ function WalletSendTip(props: Props) { React.useEffect(() => { // Regex for number up to 8 decimal places - const regexp = RegExp(/^(\d*([.]\d{0,8})?)$/); - const validTipInput = regexp.test(String(tipAmount)); + let regexp; let tipError; if (tipAmount === 0) { @@ -198,8 +199,13 @@ function WalletSendTip(props: Props) { // if it's not fiat, aka it's boost or lbc tip else if (activeTab !== TAB_FIAT) { + regexp = RegExp(/^(\d*([.]\d{0,8})?)$/); + const validTipInput = regexp.test(String(tipAmount)); + if (!validTipInput) { tipError = __('Amount must have no more than 8 decimal places'); + } else if (!validTipInput) { + tipError = __('Amount must have no more than 8 decimal places'); } else if (tipAmount === balance) { tipError = __('Please decrease the amount to account for transaction fees'); } else if (tipAmount > balance) { @@ -209,9 +215,14 @@ function WalletSendTip(props: Props) { } // if tip fiat tab } else { - if (tipAmount < 1) { + regexp = RegExp(/^(\d*([.]\d{0,2})?)$/); + const validTipInput = regexp.test(String(tipAmount)); + + if (!validTipInput) { + tipError = __('Amount must have no more than 2 decimal places'); + } else if (tipAmount < MINIMUM_FIAT_TIP) { tipError = __('Amount must be at least one dollar'); - } else if (tipAmount > 1000) { + } else if (tipAmount > MAXIMUM_FIAT_TIP) { tipError = __('Amount cannot be over 1000 dollars'); } } @@ -262,8 +273,8 @@ function WalletSendTip(props: Props) { Lbryio.call( 'customer', 'tip', - { - amount: 100 * tipAmount, // convert from dollars to cents + { // round to fix issues with floating point numbers + amount: Math.round(100 * tipAmount), // convert from dollars to cents creator_channel_name: tipChannelName, // creator_channel_name creator_channel_claim_id: channelClaimId, tipper_channel_name: sendAnonymously ? '' : activeChannelName, @@ -302,10 +313,49 @@ function WalletSendTip(props: Props) { } } - function handleCustomPriceChange(event: SyntheticInputEvent<*>) { - const tipAmount = parseFloat(event.target.value); + var countDecimals = function(value) { + var text = value.toString(); + var index = text.indexOf('.'); + return (text.length - index - 1); + } - setCustomTipAmount(tipAmount); + function handleCustomPriceChange(event: SyntheticInputEvent<*>) { + + let tipAmountAsString = event.target.value; + + let tipAmount = parseFloat(tipAmountAsString); + + const howManyDecimals = countDecimals(tipAmountAsString); + + // allow maximum two decimals + if (activeTab === TAB_FIAT) { + + if (Number.isNaN(tipAmount)) { + setCustomTipAmount(''); + } + + if (howManyDecimals > 2) { + tipAmount = Math.floor(tipAmount * 100) / 100; + } + + const howManyDigits = Math.trunc(tipAmount).toString().length; + + if (howManyDigits > 4 && tipAmount !== 1000) { + setTipError('Amount cannot be over 1000 dollars'); + } else if (tipAmount > 1000) { + setTipError('Amount cannot be over 1000 dollars'); + setCustomTipAmount(tipAmount); + } else { + setCustomTipAmount(tipAmount); + } + } else { + if (howManyDecimals > 9) { + tipAmount = Number(tipAmount.toString().match(/^-?\d+(?:\.\d{0,8})?/)[0]); + + setTipError('Please only use up to 8 decimals') + } + setCustomTipAmount(tipAmount); + } } function buildButtonText() { @@ -320,8 +370,14 @@ function WalletSendTip(props: Props) { return false; } + function convertToTwoDecimals(number){ + return (Math.round(number * 100) / 100).toFixed(2); + } + + const amountToShow = activeTab === TAB_FIAT ? convertToTwoDecimals(tipAmount) : tipAmount; + // if it's a valid number display it, otherwise do an empty string - const displayAmount = !isNan(tipAmount) ? tipAmount : ''; + const displayAmount = !isNan(tipAmount) ? amountToShow : ''; if (activeTab === TAB_BOOST) { return (claimIsMine ? __('Boost Your %claimTypeText%', {claimTypeText}) : __('Boost This %claimTypeText%', {claimTypeText})); @@ -351,7 +407,7 @@ function WalletSendTip(props: Props) { return (
{/* if there is no LBC balance, show user frontend to get credits */} - {noBalance ? ( + {1 == 2 ? ( }}>Supporting content requires %lbc%} subtitle={ @@ -433,7 +489,7 @@ function WalletSendTip(props: Props) { {/* short explainer under the button */}
- {explainerText} + {explainerText + ' '} {/* {activeTab === TAB_FIAT && !hasCardSaved &&
@@ -453,7 +509,7 @@ function WalletSendTip(props: Props) {
{setConfirmLabel()}
- {activeTab === TAB_FIAT ?

$ {tipAmount}

: } + {activeTab === TAB_FIAT ?

$ {(Math.round(tipAmount * 100) / 100).toFixed(2)}

: }
@@ -468,8 +524,10 @@ function WalletSendTip(props: Props) {