More stripe integration #6649

Closed
mayeaux wants to merge 17 commits from more-stripe-integration into master
7 changed files with 200 additions and 40 deletions

View file

@ -131,6 +131,7 @@ export function CommentCreate(props: Props) {
return; return;
} }
// if comment post didn't work, but tip was already made, try againt o create comment
if (commentFailure && tipAmount === successTip.tipAmount) { if (commentFailure && tipAmount === successTip.tipAmount) {
handleCreateComment(successTip.txid); handleCreateComment(successTip.txid);
return; return;
@ -147,27 +148,6 @@ export function CommentCreate(props: Props) {
const activeChannelName = activeChannelClaim && activeChannelClaim.name; const activeChannelName = activeChannelClaim && activeChannelClaim.name;
const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id; const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
setIsSubmitting(true);
if (activeTab === TAB_LBC) {
// call sendTip and then run the callback from the response
// second parameter is callback
sendTip(
params,
(response) => {
const { txid } = response;
// todo: why the setTimeout?
setTimeout(() => {
handleCreateComment(txid);
}, 1500);
setSuccessTip({ txid, tipAmount });
},
() => {
// reset the frontend so people can send a new comment
setIsSubmitting(false);
}
);
} else {
// setup variables for tip API // setup variables for tip API
let channelClaimId, tipChannelName; let channelClaimId, tipChannelName;
// if there is a signing channel it's on a file // if there is a signing channel it's on a file
@ -181,14 +161,46 @@ export function CommentCreate(props: Props) {
tipChannelName = claim.name; tipChannelName = claim.name;
} }
console.log(activeChannelClaim);
setIsSubmitting(true);
if (activeTab === TAB_LBC) {
// call sendTip and then run the callback from the response
// second parameter is callback
sendTip(
params,
(response) => {
const { txid } = response;
// todo: why the setTimeout?
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 });
},
() => {
// reset the frontend so people can send a new comment
setIsSubmitting(false);
}
);
} else {
const sourceClaimId = claim.claim_id; const sourceClaimId = claim.claim_id;
const roundedAmount = Math.round(tipAmount * 100) / 100; const roundedAmount = Math.round(tipAmount * 100) / 100;
Lbryio.call( Lbryio.call(
'customer', 'customer',
'tip', 'tip',
{ { // round to deal with floating point precision
amount: 100 * roundedAmount, // convert from dollars to cents amount: Math.round(100 * roundedAmount), // convert from dollars to cents
creator_channel_name: tipChannelName, // creator_channel_name creator_channel_name: tipChannelName, // creator_channel_name
creator_channel_claim_id: channelClaimId, creator_channel_claim_id: channelClaimId,
tipper_channel_name: activeChannelName, tipper_channel_name: activeChannelName,

View file

@ -22,7 +22,9 @@ type Props = {
fetchingComments: boolean, fetchingComments: boolean,
doSuperChatList: (string) => void, doSuperChatList: (string) => void,
superChats: Array<Comment>, superChats: Array<Comment>,
superChatsReversed: Array,
jessopb commented 2021-07-29 23:16:09 +02:00 (Migrated from github.com)
Review

This does'nt seem to be passed in. Does it belong in props types?

This does'nt seem to be passed in. Does it belong in props types?
superChatsTotalAmount: number, superChatsTotalAmount: number,
superChatsFiatAmount: number,
myChannels: ?Array<ChannelClaim>, myChannels: ?Array<ChannelClaim>,
}; };
@ -38,15 +40,16 @@ export default function LivestreamComments(props: Props) {
embed, embed,
doCommentSocketConnect, doCommentSocketConnect,
doCommentSocketDisconnect, doCommentSocketDisconnect,
comments, comments, // superchats in chronological format
doCommentList, doCommentList,
fetchingComments, fetchingComments,
doSuperChatList, doSuperChatList,
superChats,
superChatsTotalAmount,
myChannels, myChannels,
superChats, // superchats organized by tip amount
} = props; } = props;
let { superChatsReversed, superChatsFiatAmount, superChatsTotalAmount } = props;
const commentsRef = React.createRef(); const commentsRef = React.createRef();
const [scrollBottom, setScrollBottom] = React.useState(true); const [scrollBottom, setScrollBottom] = React.useState(true);
const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT); 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 discussionElement = document.querySelector('.livestream__comments');
jessopb commented 2021-07-29 23:17:20 +02:00 (Migrated from github.com)
Review

One thing you can do is get
{ superChats: superchatsSortedByAmount, comments: superchatsSortedByDate } = props; to use a new name after destructuring.

One thing you can do is get { superChats: superchatsSortedByAmount, comments: superchatsSortedByDate } = props; to use a new name after destructuring.
jessopb commented 2021-07-29 23:19:59 +02:00 (Migrated from github.com)
Review

Perhaps whatever is connect()ed in in the index.js could just have a better name.

Perhaps whatever is connect()ed in in the index.js could just have a better name.
const commentElement = document.querySelector('.livestream-comment'); 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 // todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine
function isMyComment(channelId: string) { function isMyComment(channelId: string) {
if (myChannels != null && channelId != null) { if (myChannels != null && channelId != null) {
@ -71,6 +104,7 @@ export default function LivestreamComments(props: Props) {
} }
React.useEffect(() => { React.useEffect(() => {
if (claimId) { if (claimId) {
doCommentList(uri, '', 1, 75); doCommentList(uri, '', 1, 75);
doSuperChatList(uri); doSuperChatList(uri);
@ -132,24 +166,38 @@ export default function LivestreamComments(props: Props) {
<div className="livestream-discussion__title">{__('Live discussion')}</div> <div className="livestream-discussion__title">{__('Live discussion')}</div>
{superChatsTotalAmount > 0 && ( {superChatsTotalAmount > 0 && (
<div className="recommended-content__toggles"> <div className="recommended-content__toggles">
{/* the superchats in chronological order button */}
<Button <Button
className={classnames('button-toggle', { className={classnames('button-toggle', {
'button-toggle--active': viewMode === VIEW_MODE_CHAT, 'button-toggle--active': viewMode === VIEW_MODE_CHAT,
})} })}
label={__('Chat')} label={__('Chat')}
onClick={() => setViewMode(VIEW_MODE_CHAT)} onClick={function() {
setViewMode(VIEW_MODE_CHAT);
const livestreamCommentsDiv = document.getElementsByClassName('livestream__comments')[0]
const divHeight = livestreamCommentsDiv.scrollHeight;
livestreamCommentsDiv.scrollTop = divHeight;
}}
/> />
{/* the button to show superchats listed by most to least support amount */}
<Button <Button
className={classnames('button-toggle', { className={classnames('button-toggle', {
'button-toggle--active': viewMode === VIEW_MODE_SUPER_CHAT, 'button-toggle--active': viewMode === VIEW_MODE_SUPER_CHAT,
})} })}
label={ label={
<> <>
<CreditAmount amount={superChatsTotalAmount} size={8} /> {__('Tipped')} <CreditAmount amount={superChatsTotalAmount} size={8} /> /
<CreditAmount amount={superChatsFiatAmount} size={8} isFiat={true} /> {' '}{__('Tipped')}
</> </>
} }
onClick={() => setViewMode(VIEW_MODE_SUPER_CHAT)} onClick={function() {
setViewMode(VIEW_MODE_SUPER_CHAT);
const livestreamCommentsDiv = document.getElementsByClassName('livestream__comments')[0]
const divHeight = livestreamCommentsDiv.scrollHeight;
livestreamCommentsDiv.scrollTop = divHeight * -1;
}}
/> />
</div> </div>
)} )}
@ -187,9 +235,23 @@ export default function LivestreamComments(props: Props) {
</div> </div>
)} )}
{/* top to bottom comment display */}
{!fetchingComments && comments.length > 0 ? ( {!fetchingComments && comments.length > 0 ? (
<div className="livestream__comments"> <div className="livestream__comments">
{commentsToDisplay.map((comment) => ( {viewMode === VIEW_MODE_CHAT && commentsToDisplay.map((comment) => (
<LivestreamComment
key={comment.comment_id}
uri={uri}
authorUri={comment.channel_url}
commentId={comment.comment_id}
message={comment.comment}
supportAmount={comment.support_amount}
isFiat={comment.is_fiat}
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
/>
))}
{viewMode === VIEW_MODE_SUPER_CHAT && superChatsReversed && superChatsReversed.map((comment) => (
<LivestreamComment <LivestreamComment
key={comment.comment_id} key={comment.comment_id}
uri={uri} uri={uri}

View file

@ -273,8 +273,8 @@ function WalletSendTip(props: Props) {
Lbryio.call( Lbryio.call(
jessopb commented 2021-07-29 23:06:52 +02:00 (Migrated from github.com)
Review

We should try to move this component to /web at least. Ideally This is a function imported from lbryinc repo.

We should try to move this component to /web at least. Ideally This is a function imported from lbryinc repo.
'customer', 'customer',
'tip', 'tip',
{ { // round to fix issues with floating point numbers
amount: 100 * tipAmount, // convert from dollars to cents amount: Math.round(100 * tipAmount), // convert from dollars to cents
creator_channel_name: tipChannelName, // creator_channel_name creator_channel_name: tipChannelName, // creator_channel_name
creator_channel_claim_id: channelClaimId, creator_channel_claim_id: channelClaimId,
tipper_channel_name: sendAnonymously ? '' : activeChannelName, tipper_channel_name: sendAnonymously ? '' : activeChannelName,
@ -313,10 +313,49 @@ function WalletSendTip(props: Props) {
} }
} }
function handleCustomPriceChange(event: SyntheticInputEvent<*>) { var countDecimals = function(value) {
const tipAmount = parseFloat(event.target.value); var text = value.toString();
var index = text.indexOf('.');
return (text.length - index - 1);
}
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); 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')
}
jessopb commented 2021-07-29 23:45:28 +02:00 (Migrated from github.com)
Review

MAXIMUM_FIAT_TIP

MAXIMUM_FIAT_TIP
setCustomTipAmount(tipAmount);
jessopb commented 2021-07-29 23:46:10 +02:00 (Migrated from github.com)
Review

repeated text, without i18n

repeated text, without i18n
}
} }
function buildButtonText() { function buildButtonText() {
@ -331,8 +370,14 @@ function WalletSendTip(props: Props) {
return false; 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 // 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) { if (activeTab === TAB_BOOST) {
return (claimIsMine ? __('Boost Your %claimTypeText%', {claimTypeText}) : __('Boost This %claimTypeText%', {claimTypeText})); return (claimIsMine ? __('Boost Your %claimTypeText%', {claimTypeText}) : __('Boost This %claimTypeText%', {claimTypeText}));
@ -362,7 +407,7 @@ function WalletSendTip(props: Props) {
return ( return (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
{/* if there is no LBC balance, show user frontend to get credits */} {/* if there is no LBC balance, show user frontend to get credits */}
{noBalance ? ( {1 == 2 ? (
<Card <Card
title={<I18nMessage tokens={{ lbc: <LbcSymbol size={22} /> }}>Supporting content requires %lbc%</I18nMessage>} title={<I18nMessage tokens={{ lbc: <LbcSymbol size={22} /> }}>Supporting content requires %lbc%</I18nMessage>}
subtitle={ subtitle={
@ -444,7 +489,7 @@ function WalletSendTip(props: Props) {
{/* short explainer under the button */} {/* short explainer under the button */}
jessopb commented 2021-07-29 23:08:53 +02:00 (Migrated from github.com)
Review

artifact

artifact
<div className="section__subtitle"> <div className="section__subtitle">
{explainerText} {explainerText + ' '}
{/* {activeTab === TAB_FIAT && !hasCardSaved && <Button navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} label={__('Add A Card')} button="link" />} */} {/* {activeTab === TAB_FIAT && !hasCardSaved && <Button navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} label={__('Add A Card')} button="link" />} */}
{<Button label={__('Learn more')} button="link" href="https://lbry.com/faq/tipping" />} {<Button label={__('Learn more')} button="link" href="https://lbry.com/faq/tipping" />}
</div> </div>
@ -464,7 +509,7 @@ function WalletSendTip(props: Props) {
</div> </div>
<div className="confirm__label">{setConfirmLabel()}</div> <div className="confirm__label">{setConfirmLabel()}</div>
<div className="confirm__value"> <div className="confirm__value">
{activeTab === TAB_FIAT ? <p>$ {tipAmount}</p> : <LbcSymbol postfix={tipAmount} size={22} />} {activeTab === TAB_FIAT ? <p>$ {(Math.round(tipAmount * 100) / 100).toFixed(2)}</p> : <LbcSymbol postfix={tipAmount} size={22} />}
</div> </div>
</div> </div>
</div> </div>
@ -479,8 +524,10 @@ function WalletSendTip(props: Props) {
<Button button="link" label={__('Cancel')} onClick={() => setIsConfirming(false)} /> <Button button="link" label={__('Cancel')} onClick={() => setIsConfirming(false)} />
</div> </div>
</> </>
) : ( // only show the prompt to earn more if its lbc or boost tab and no balance
<> // otherwise you can show the full prompt
) : (!((activeTab === TAB_LBC || activeTab === TAB_BOOST) && noBalance)
? <>
<div className="section"> <div className="section">
<ChannelSelector /> <ChannelSelector />
</div> </div>
@ -559,6 +606,9 @@ function WalletSendTip(props: Props) {
min="0" min="0"
step="any" step="any"
type="number" type="number"
style={{
width: activeTab === TAB_FIAT ? '99px' : '160px',
}}
placeholder="1.23" placeholder="1.23"
value={customTipAmount} value={customTipAmount}
onChange={(event) => handleCustomPriceChange(event)} onChange={(event) => handleCustomPriceChange(event)}
@ -591,7 +641,28 @@ function WalletSendTip(props: Props) {
) : ( ) : (
<div className="help">{__('The payment will be made from your saved card')}</div> <div className="help">{__('The payment will be made from your saved card')}</div>
)} )}
</> </> : <>
<Card
title={<I18nMessage tokens={{ lbc: <LbcSymbol size={22} /> }}>Supporting content requires %lbc%</I18nMessage>}
subtitle={
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>
With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to
see.
</I18nMessage>
}
actions={
<div className="section__actions">
<Button
icon={ICONS.REWARDS}
button="primary"
label={__('Earn Rewards')}
navigate={`/$/${PAGES.REWARDS}`}
/>
<Button icon={ICONS.BUY} button="secondary" label={__('Buy/Swap Credits')} navigate={`/$/${PAGES.BUY}`} />
<Button button="link" label={__('Nevermind')} onClick={closeModal} />
</div>
}
/></>
) )
} }
/> />

View file

@ -2,12 +2,15 @@ import { connect } from 'react-redux';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import StripeAccountConnection from './view'; import StripeAccountConnection from './view';
import { selectUser } from 'redux/selectors/user'; import { selectUser } from 'redux/selectors/user';
import { doToast } from 'redux/actions/notifications';
// function that receives state parameter and returns object of functions that accept state // function that receives state parameter and returns object of functions that accept state
const select = (state) => ({ const select = (state) => ({
user: selectUser(state), user: selectUser(state),
}); });
// const perform = (dispatch) => ({}); const perform = (dispatch) => ({
doToast: (options) => dispatch(doToast(options)),
});
export default withRouter(connect(select)(StripeAccountConnection)); export default withRouter(connect(select, perform)(StripeAccountConnection));

View file

@ -31,6 +31,7 @@ if (isDev) {
type Props = { type Props = {
source: string, source: string,
user: User, user: User,
doOpenModal: (string, {}) => void,
}; };
type State = { type State = {
@ -68,6 +69,8 @@ class StripeAccountConnection extends React.Component<Props, State> {
componentDidMount() { componentDidMount() {
const { user } = this.props; const { user } = this.props;
let doToast = this.props.doToast;
// $FlowFixMe // $FlowFixMe
this.experimentalUiEnabled = user && user.experimental_ui; this.experimentalUiEnabled = user && user.experimental_ui;
@ -127,7 +130,7 @@ class StripeAccountConnection extends React.Component<Props, State> {
).then((accountListResponse: any) => { ).then((accountListResponse: any) => {
// TODO type this // TODO type this
that.setState({ that.setState({
accountTransactions: accountListResponse, accountTransactions: accountListResponse.reverse(),
}); });
console.log(accountListResponse); console.log(accountListResponse);
@ -165,9 +168,13 @@ class StripeAccountConnection extends React.Component<Props, State> {
// get stripe link and set it on the frontend // get stripe link and set it on the frontend
getAndSetAccountLink(true); getAndSetAccountLink(true);
} else { } else {
// probably an error from stripe
var displayString = 'There was an error getting your account setup, please let support know';
doToast({ message: displayString, isError: true });
// not an error from Beamer, throw it // not an error from Beamer, throw it
throw new Error(error); throw new Error(error);
} }
}); });
} }
@ -296,7 +303,7 @@ class StripeAccountConnection extends React.Component<Props, State> {
</thead> </thead>
<tbody> <tbody>
{accountTransactions && {accountTransactions &&
accountTransactions.reverse().map((transaction) => ( accountTransactions.map((transaction) => (
<tr key={transaction.name + transaction.created_at}> <tr key={transaction.name + transaction.created_at}>
<td>{moment(transaction.created_at).format('LLL')}</td> <td>{moment(transaction.created_at).format('LLL')}</td>
<td> <td>

View file

@ -188,10 +188,15 @@ class SettingsStripeCard extends React.Component<Props, State> {
// instantiate stripe elements // instantiate stripe elements
setupStripe(); setupStripe();
}); });
// 500 error from the backend being down
} else if (error === 'internal_apis_down') { } else if (error === 'internal_apis_down') {
var displayString = 'There was an error from the server, please let support know'; var displayString = 'There was an error from the server, please let support know';
doToast({ message: displayString, isError: true }); doToast({ message: displayString, isError: true });
} else { } else {
// probably an error from stripe
var displayString = 'There was an error getting your card setup, please let support know';
jessopb commented 2021-07-29 22:59:34 +02:00 (Migrated from github.com)
Review

var -> let
Errors could probably be consts at the top?
API_DOWN_ERROR_MESSAGE
CARD_ERROR_MESSAGE

var -> let Errors could probably be consts at the top? API_DOWN_ERROR_MESSAGE CARD_ERROR_MESSAGE
doToast({ message: displayString, isError: true });
console.log('Unseen before error'); console.log('Unseen before error');
jessopb commented 2021-07-29 22:59:40 +02:00 (Migrated from github.com)
Review

log?

log?
} }
}); });

View file

@ -404,9 +404,9 @@ fieldset-group {
} }
} }
.form-field--price-amount { //.form-field--price-amount {
max-width: 6em; // max-width: 6em;
} //}
.form-field--price-amount--auto { .form-field--price-amount--auto {
width: auto; width: auto;