Stripe 2
show visible card and add remove card button show your transactions even if you dont have a card fix presentational issues show your transactions even if you dont have a card fix presentational issues add link to channel section update yarn show donation location add remove card modal still needs completion and also changed how stripe is used on settings stripe card page add confirm remove card modal to router move bank account stuff to settings page move account functionality to settings page continuing to move account transactions to settings list transactions for creator updating copy touchup tip error do a better job autofocusing bugfix show an error on the card page if api returns 500 building out frontend for comment tip display dollar sign if its a fiat tip more frontend work more frontend work more frontend bug fixes working with hardcoded payment intent id working but with one bug bugfixed add toast if payment fails add add card button cant get claim id but otherwise done more frontend work call is working show fiat for livestream comments add is fiat on comments round and show values properly dont allow review if tiperror copy displaying properly disable buttons conditionally properly remove card button working remove card working with a workaround by refreshing page bugfix send toast when tip on comment jeremy frontend changes only show cart on lbc
This commit is contained in:
parent
cd32fb71c7
commit
7bb5df97fd
27 changed files with 1112 additions and 209 deletions
|
@ -56,6 +56,7 @@
|
||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
"if-env": "^1.0.4",
|
"if-env": "^1.0.4",
|
||||||
"react-datetime-picker": "^3.2.1",
|
"react-datetime-picker": "^3.2.1",
|
||||||
|
"react-plastic": "^1.1.1",
|
||||||
"react-top-loading-bar": "^2.0.1",
|
"react-top-loading-bar": "^2.0.1",
|
||||||
"remove-markdown": "^0.3.0",
|
"remove-markdown": "^0.3.0",
|
||||||
"source-map-explorer": "^2.5.2",
|
"source-map-explorer": "^2.5.2",
|
||||||
|
|
|
@ -2026,7 +2026,7 @@
|
||||||
"Supporting content requires %lbc%": "Supporting content requires %lbc%",
|
"Supporting content requires %lbc%": "Supporting content requires %lbc%",
|
||||||
"With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.": "With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.",
|
"With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.": "With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.",
|
||||||
"This refundable boost will improve the discoverability of this content while active.": "This refundable boost will improve the discoverability of this content while active.",
|
"This refundable boost will improve the discoverability of this content while active.": "This refundable boost will improve the discoverability of this content while active.",
|
||||||
"Show this channel your appreciation by sending a donation of cash in USD.": "Show this channel your appreciation by sending a donation of cash in USD.",
|
"Show this channel your appreciation by sending a donation in USD.": "Show this channel your appreciation by sending a donation in USD.",
|
||||||
"Show this channel your appreciation by sending a donation of Credits.": "Show this channel your appreciation by sending a donation of Credits.",
|
"Show this channel your appreciation by sending a donation of Credits.": "Show this channel your appreciation by sending a donation of Credits.",
|
||||||
"Add card to tip creators in USD": "Add card to tip creators in USD",
|
"Add card to tip creators in USD": "Add card to tip creators in USD",
|
||||||
"Connect a bank account": "Connect a bank account",
|
"Connect a bank account": "Connect a bank account",
|
||||||
|
|
|
@ -59,6 +59,7 @@ type Props = {
|
||||||
stakedLevel: number,
|
stakedLevel: number,
|
||||||
supportAmount: number,
|
supportAmount: number,
|
||||||
numDirectReplies: number,
|
numDirectReplies: number,
|
||||||
|
isFiat: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
const LENGTH_TO_COLLAPSE = 300;
|
const LENGTH_TO_COLLAPSE = 300;
|
||||||
|
@ -91,6 +92,7 @@ function Comment(props: Props) {
|
||||||
stakedLevel,
|
stakedLevel,
|
||||||
supportAmount,
|
supportAmount,
|
||||||
numDirectReplies,
|
numDirectReplies,
|
||||||
|
isFiat,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -240,7 +242,7 @@ function Comment(props: Props) {
|
||||||
label={<DateTime date={timePosted} timeAgo />}
|
label={<DateTime date={timePosted} timeAgo />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{supportAmount > 0 && <CreditAmount amount={supportAmount} superChatLight size={12} />}
|
{supportAmount > 0 && <CreditAmount isFiat={isFiat} amount={supportAmount} superChatLight size={12} />}
|
||||||
|
|
||||||
{isPinned && (
|
{isPinned && (
|
||||||
<span className="comment__pin">
|
<span className="comment__pin">
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
import { makeSelectCommentsDisabledForUri } from 'redux/selectors/comments';
|
import { makeSelectCommentsDisabledForUri } from 'redux/selectors/comments';
|
||||||
import { CommentCreate } from './view';
|
import { CommentCreate } from './view';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
|
@ -24,11 +25,12 @@ const select = (state, props) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch, ownProps) => ({
|
const perform = (dispatch, ownProps) => ({
|
||||||
createComment: (comment, claimId, parentId, txid) =>
|
createComment: (comment, claimId, parentId, txid, payment_intent_id, environment) =>
|
||||||
dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, ownProps.livestream, txid)),
|
dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, ownProps.livestream, txid, payment_intent_id, environment)),
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)),
|
setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)),
|
||||||
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
|
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
|
||||||
|
doToast: (options) => dispatch(doToast(options)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(CommentCreate);
|
export default connect(select, perform)(CommentCreate);
|
||||||
|
|
|
@ -16,11 +16,23 @@ import CreditAmount from 'component/common/credit-amount';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
import Empty from 'component/common/empty';
|
import Empty from 'component/common/empty';
|
||||||
|
import { STRIPE_PUBLIC_KEY } from 'config';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
|
||||||
|
let stripeEnvironment = 'test';
|
||||||
|
// if the key contains pk_live it's a live key
|
||||||
|
// update the environment for the calls to the backend to indicate which environment to hit
|
||||||
|
if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) {
|
||||||
|
stripeEnvironment = 'live';
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAB_FIAT = 'TabFiat';
|
||||||
|
const TAB_LBC = 'TabLBC';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
createComment: (string, string, string, ?string) => Promise<any>,
|
createComment: (string, string, string, ?string, ?string, ?string) => Promise<any>,
|
||||||
commentsDisabledBySettings: boolean,
|
commentsDisabledBySettings: boolean,
|
||||||
channels: ?Array<ChannelClaim>,
|
channels: ?Array<ChannelClaim>,
|
||||||
onDoneReplying?: () => void,
|
onDoneReplying?: () => void,
|
||||||
|
@ -35,6 +47,8 @@ type Props = {
|
||||||
toast: (string) => void,
|
toast: (string) => void,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
sendTip: ({}, (any) => void, (any) => void) => void,
|
sendTip: ({}, (any) => void, (any) => void) => void,
|
||||||
|
doToast: ({ message: string }) => void,
|
||||||
|
disabled: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentCreate(props: Props) {
|
export function CommentCreate(props: Props) {
|
||||||
|
@ -53,8 +67,10 @@ export function CommentCreate(props: Props) {
|
||||||
livestream,
|
livestream,
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
sendTip,
|
sendTip,
|
||||||
|
doToast,
|
||||||
} = props;
|
} = props;
|
||||||
const buttonref: ElementRef<any> = React.useRef();
|
const buttonref: ElementRef<any> = React.useRef();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
push,
|
push,
|
||||||
location: { pathname },
|
location: { pathname },
|
||||||
|
@ -72,6 +88,14 @@ export function CommentCreate(props: Props) {
|
||||||
const disabled = isSubmitting || !activeChannelClaim || !commentValue.length;
|
const disabled = isSubmitting || !activeChannelClaim || !commentValue.length;
|
||||||
const charCount = commentValue.length;
|
const charCount = commentValue.length;
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = React.useState('');
|
||||||
|
|
||||||
|
const [tipError, setTipError] = React.useState();
|
||||||
|
|
||||||
|
// React.useEffect(() => {
|
||||||
|
// setTipError('yes');
|
||||||
|
// }, []);
|
||||||
|
|
||||||
function handleCommentChange(event) {
|
function handleCommentChange(event) {
|
||||||
let commentValue;
|
let commentValue;
|
||||||
if (isReply) {
|
if (isReply) {
|
||||||
|
@ -123,26 +147,109 @@ export function CommentCreate(props: Props) {
|
||||||
channel_id: activeChannelClaim.claim_id,
|
channel_id: activeChannelClaim.claim_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||||
|
const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
|
||||||
|
|
||||||
|
console.log(activeChannelClaim);
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
if (activeTab === TAB_LBC) {
|
||||||
|
// call sendTip and then run the callback from the response
|
||||||
|
// second parameter is callback
|
||||||
sendTip(
|
sendTip(
|
||||||
params,
|
params,
|
||||||
(response) => {
|
(response) => {
|
||||||
const { txid } = response;
|
const { txid } = response;
|
||||||
|
// todo: why the setTimeout?
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
handleCreateComment(txid);
|
handleCreateComment(txid);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
setSuccessTip({ txid, tipAmount });
|
setSuccessTip({ txid, tipAmount });
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
// reset the frontend so people can send a new comment
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCreateComment(txid) {
|
const sourceClaimId = claim.claim_id;
|
||||||
|
|
||||||
|
var roundedAmount = Math.round(tipAmount * 100) / 100;
|
||||||
|
|
||||||
|
Lbryio.call(
|
||||||
|
'customer',
|
||||||
|
'tip',
|
||||||
|
{
|
||||||
|
amount: 100 * roundedAmount, // convert from dollars to cents
|
||||||
|
creator_channel_name: tipChannelName, // creator_channel_name
|
||||||
|
creator_channel_claim_id: channelClaimId,
|
||||||
|
tipper_channel_name: activeChannelName,
|
||||||
|
tipper_channel_claim_id: activeChannelId,
|
||||||
|
currency: 'USD',
|
||||||
|
anonymous: false,
|
||||||
|
source_claim_id: sourceClaimId,
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
)
|
||||||
|
.then((customerTipResponse) => {
|
||||||
|
console.log(customerTipResponse);
|
||||||
|
|
||||||
|
const paymentIntendId = customerTipResponse.payment_intent_id;
|
||||||
|
|
||||||
|
handleCreateComment(null, paymentIntendId, stripeEnvironment);
|
||||||
|
|
||||||
|
setCommentValue('');
|
||||||
|
setIsReviewingSupportComment(false);
|
||||||
|
setIsSupportComment(false);
|
||||||
|
setCommentFailure(false);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
|
||||||
|
doToast({
|
||||||
|
message: __("You sent $%formattedAmount% as a tip to %tipChannelName%, I'm sure they appreciate it!", {
|
||||||
|
formattedAmount: roundedAmount.toFixed(2), // force show decimal places
|
||||||
|
tipChannelName,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// handleCreateComment(null);
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
var displayError = 'Sorry, there was an error in processing your payment!';
|
||||||
|
|
||||||
|
if (error.message !== 'payment intent failed to confirm') {
|
||||||
|
displayError = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
doToast({ message: displayError, isError: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} [txid] Optional transaction id generated by
|
||||||
|
* @param {string} [payment_intent_id] Optional payment_intent_id from Stripe payment
|
||||||
|
* @param {string} [environment] Optional environment for Stripe (test|live)
|
||||||
|
*/
|
||||||
|
function handleCreateComment(txid, payment_intent_id, environment) {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
createComment(commentValue, claimId, parentId, txid)
|
|
||||||
|
createComment(commentValue, claimId, parentId, txid, payment_intent_id, environment)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
||||||
|
@ -157,7 +264,7 @@ export function CommentCreate(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
setCommentFailure(true);
|
setCommentFailure(true);
|
||||||
});
|
});
|
||||||
|
@ -198,10 +305,11 @@ export function CommentCreate(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isReviewingSupportComment && activeChannelClaim) {
|
if (isReviewingSupportComment && activeChannelClaim) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="comment__create">
|
<div className="comment__create">
|
||||||
<div className="comment__sc-preview">
|
<div className="comment__sc-preview">
|
||||||
<CreditAmount className="comment__scpreview-amount" amount={tipAmount} size={18} />
|
<CreditAmount className="comment__scpreview-amount" isFiat={activeTab === TAB_FIAT} amount={tipAmount} size={activeTab === TAB_LBC ? 18 : 2} />
|
||||||
|
|
||||||
<ChannelThumbnail xsmall uri={activeChannelClaim.canonical_url} />
|
<ChannelThumbnail xsmall uri={activeChannelClaim.canonical_url} />
|
||||||
<div>
|
<div>
|
||||||
|
@ -262,15 +370,17 @@ export function CommentCreate(props: Props) {
|
||||||
autoFocus={isReply}
|
autoFocus={isReply}
|
||||||
textAreaMaxLength={livestream ? FF_MAX_CHARS_IN_LIVESTREAM_COMMENT : FF_MAX_CHARS_IN_COMMENT}
|
textAreaMaxLength={livestream ? FF_MAX_CHARS_IN_LIVESTREAM_COMMENT : FF_MAX_CHARS_IN_COMMENT}
|
||||||
/>
|
/>
|
||||||
{isSupportComment && <WalletTipAmountSelector amount={tipAmount} onChange={(amount) => setTipAmount(amount)} />}
|
{/* TODO: the tip validation is done in selector */}
|
||||||
|
{isSupportComment && <WalletTipAmountSelector onTipErrorChange={setTipError} claim={claim} activeTab={activeTab} amount={tipAmount} onChange={(amount) => setTipAmount(amount)} />}
|
||||||
<div className="section__actions section__actions--no-margin">
|
<div className="section__actions section__actions--no-margin">
|
||||||
{isSupportComment ? (
|
{isSupportComment ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
// TODO: add better check here
|
||||||
|
disabled={disabled || tipError}
|
||||||
type="button"
|
type="button"
|
||||||
button="primary"
|
button="primary"
|
||||||
icon={ICONS.LBC}
|
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
|
||||||
label={__('Review')}
|
label={__('Review')}
|
||||||
onClick={() => setIsReviewingSupportComment(true)}
|
onClick={() => setIsReviewingSupportComment(true)}
|
||||||
/>
|
/>
|
||||||
|
@ -296,7 +406,16 @@ export function CommentCreate(props: Props) {
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
/>
|
/>
|
||||||
{!claimIsMine && (
|
{!claimIsMine && (
|
||||||
<Button disabled={disabled} button="alt" icon={ICONS.LBC} onClick={() => setIsSupportComment(true)} />
|
<Button disabled={disabled} button="alt" className="thatButton" icon={ICONS.LBC} onClick={() => {
|
||||||
|
setIsSupportComment(true);
|
||||||
|
setActiveTab(TAB_LBC);
|
||||||
|
}} />
|
||||||
|
)}
|
||||||
|
{!claimIsMine && (
|
||||||
|
<Button disabled={disabled} button="alt" className="thisButton" icon={ICONS.FINANCE} onClick={() => {
|
||||||
|
setIsSupportComment(true);
|
||||||
|
setActiveTab(TAB_FIAT);
|
||||||
|
}} />
|
||||||
)}
|
)}
|
||||||
{isReply && (
|
{isReply && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -307,6 +307,7 @@ function CommentList(props: Props) {
|
||||||
isPinned={comment.is_pinned}
|
isPinned={comment.is_pinned}
|
||||||
supportAmount={comment.support_amount}
|
supportAmount={comment.support_amount}
|
||||||
numDirectReplies={comment.replies}
|
numDirectReplies={comment.replies}
|
||||||
|
isFiat={comment.is_fiat}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -18,6 +18,7 @@ type Props = {
|
||||||
size?: number,
|
size?: number,
|
||||||
superChat?: boolean,
|
superChat?: boolean,
|
||||||
superChatLight?: boolean,
|
superChatLight?: boolean,
|
||||||
|
isFiat?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class CreditAmount extends React.PureComponent<Props> {
|
class CreditAmount extends React.PureComponent<Props> {
|
||||||
|
@ -45,6 +46,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
||||||
size,
|
size,
|
||||||
superChat,
|
superChat,
|
||||||
superChatLight,
|
superChatLight,
|
||||||
|
isFiat,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const minimumRenderableAmount = 10 ** (-1 * precision);
|
const minimumRenderableAmount = 10 ** (-1 * precision);
|
||||||
const fullPrice = formatFullPrice(amount, 2);
|
const fullPrice = formatFullPrice(amount, 2);
|
||||||
|
@ -70,8 +72,10 @@ class CreditAmount extends React.PureComponent<Props> {
|
||||||
amountText = `+${amountText}`;
|
amountText = `+${amountText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showLBC) {
|
if (showLBC && !isFiat) {
|
||||||
amountText = <LbcSymbol postfix={amountText} size={size} />;
|
amountText = <LbcSymbol postfix={amountText} size={size} />;
|
||||||
|
} else if (showLBC && isFiat) {
|
||||||
|
amountText = <p> ${(Math.round(amountText * 100) / 100).toFixed(2)}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fee) {
|
if (fee) {
|
||||||
|
|
|
@ -20,10 +20,11 @@ type Props = {
|
||||||
commentIsMine: boolean,
|
commentIsMine: boolean,
|
||||||
stakedLevel: number,
|
stakedLevel: number,
|
||||||
supportAmount: number,
|
supportAmount: number,
|
||||||
|
isFiat: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function LivestreamComment(props: Props) {
|
function LivestreamComment(props: Props) {
|
||||||
const { claim, uri, authorUri, message, commentIsMine, commentId, stakedLevel, supportAmount } = props;
|
const { claim, uri, authorUri, message, commentIsMine, commentId, stakedLevel, supportAmount, isFiat } = props;
|
||||||
const [mouseIsHovering, setMouseHover] = React.useState(false);
|
const [mouseIsHovering, setMouseHover] = React.useState(false);
|
||||||
const commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
|
const commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
|
||||||
const { claimName } = parseURI(authorUri);
|
const { claimName } = parseURI(authorUri);
|
||||||
|
@ -39,7 +40,7 @@ function LivestreamComment(props: Props) {
|
||||||
{supportAmount > 0 && (
|
{supportAmount > 0 && (
|
||||||
<div className="super-chat livestream-superchat__banner">
|
<div className="super-chat livestream-superchat__banner">
|
||||||
<div className="livestream-superchat__banner-corner" />
|
<div className="livestream-superchat__banner-corner" />
|
||||||
<CreditAmount amount={supportAmount} superChat className="livestream-superchat__amount" />
|
<CreditAmount isFiat={isFiat} amount={supportAmount} superChat className="livestream-superchat__amount" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ export default function LivestreamComments(props: Props) {
|
||||||
superChatsTotalAmount,
|
superChatsTotalAmount,
|
||||||
myChannels,
|
myChannels,
|
||||||
} = props;
|
} = 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);
|
||||||
|
@ -174,6 +175,7 @@ export default function LivestreamComments(props: Props) {
|
||||||
size={10}
|
size={10}
|
||||||
className="livestream-superchat__amount-large"
|
className="livestream-superchat__amount-large"
|
||||||
amount={superChat.support_amount}
|
amount={superChat.support_amount}
|
||||||
|
isFiat={superChat.is_fiat}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -193,6 +195,7 @@ export default function LivestreamComments(props: Props) {
|
||||||
commentId={comment.comment_id}
|
commentId={comment.comment_id}
|
||||||
message={comment.comment}
|
message={comment.comment}
|
||||||
supportAmount={comment.support_amount}
|
supportAmount={comment.support_amount}
|
||||||
|
isFiat={comment.is_fiat}
|
||||||
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
|
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -69,6 +69,7 @@ const RewardsVerifyPage = lazyImport(() => import('page/rewardsVerify' /* webpac
|
||||||
const SearchPage = lazyImport(() => import('page/search' /* webpackChunkName: "secondary" */));
|
const SearchPage = lazyImport(() => import('page/search' /* webpackChunkName: "secondary" */));
|
||||||
const SettingsAdvancedPage = lazyImport(() => import('page/settingsAdvanced' /* webpackChunkName: "secondary" */));
|
const SettingsAdvancedPage = lazyImport(() => import('page/settingsAdvanced' /* webpackChunkName: "secondary" */));
|
||||||
const SettingsStripeCard = lazyImport(() => import('page/settingsStripeCard' /* webpackChunkName: "secondary" */));
|
const SettingsStripeCard = lazyImport(() => import('page/settingsStripeCard' /* webpackChunkName: "secondary" */));
|
||||||
|
const SettingsStripeAccount = lazyImport(() => import('page/settingsStripeAccount' /* webpackChunkName: "secondary" */));
|
||||||
const SettingsCreatorPage = lazyImport(() => import('page/settingsCreator' /* webpackChunkName: "secondary" */));
|
const SettingsCreatorPage = lazyImport(() => import('page/settingsCreator' /* webpackChunkName: "secondary" */));
|
||||||
const SettingsNotificationsPage = lazyImport(() =>
|
const SettingsNotificationsPage = lazyImport(() =>
|
||||||
import('page/settingsNotifications' /* webpackChunkName: "secondary" */)
|
import('page/settingsNotifications' /* webpackChunkName: "secondary" */)
|
||||||
|
@ -292,6 +293,7 @@ function AppRouter(props: Props) {
|
||||||
/>
|
/>
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`} component={SettingsNotificationsPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`} component={SettingsNotificationsPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} component={SettingsStripeCard} />
|
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} component={SettingsStripeCard} />
|
||||||
|
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_STRIPE_ACCOUNT}`} component={SettingsStripeAccount} />
|
||||||
<PrivateRoute
|
<PrivateRoute
|
||||||
{...props}
|
{...props}
|
||||||
exact
|
exact
|
||||||
|
|
|
@ -96,10 +96,7 @@ function WalletSendTip(props: Props) {
|
||||||
|
|
||||||
const sourceClaimId = claim.claim_id;
|
const sourceClaimId = claim.claim_id;
|
||||||
|
|
||||||
// TODO: come up with a better way to do this,
|
// check if creator has a payment method saved
|
||||||
// TODO: waiting 100ms to wait for token to populate
|
|
||||||
|
|
||||||
// check if creator has an account saved
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (channelClaimId && isAuthenticated) {
|
if (channelClaimId && isAuthenticated) {
|
||||||
Lbryio.call(
|
Lbryio.call(
|
||||||
|
@ -121,6 +118,12 @@ function WalletSendTip(props: Props) {
|
||||||
}
|
}
|
||||||
}, [channelClaimId, isAuthenticated]);
|
}, [channelClaimId, isAuthenticated]);
|
||||||
|
|
||||||
|
// check if creator has an account saved
|
||||||
|
React.useEffect(() => {
|
||||||
|
var tipInputElement = document.getElementById('tip-input');
|
||||||
|
if (tipInputElement) { tipInputElement.focus() }
|
||||||
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (channelClaimId) {
|
if (channelClaimId) {
|
||||||
Lbryio.call(
|
Lbryio.call(
|
||||||
|
@ -139,7 +142,7 @@ function WalletSendTip(props: Props) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [channelClaimId]);
|
}, [channelClaimId]);
|
||||||
|
@ -170,7 +173,7 @@ function WalletSendTip(props: Props) {
|
||||||
explainerText = __('This refundable boost will improve the discoverability of this %claimTypeText% while active.', {claimTypeText});
|
explainerText = __('This refundable boost will improve the discoverability of this %claimTypeText% while active.', {claimTypeText});
|
||||||
} else if (activeTab === TAB_FIAT) {
|
} else if (activeTab === TAB_FIAT) {
|
||||||
iconToUse = ICONS.FINANCE;
|
iconToUse = ICONS.FINANCE;
|
||||||
explainerText = __('Show this channel your appreciation by sending a donation of cash in USD.');
|
explainerText = __('Show this channel your appreciation by sending a donation in USD. ');
|
||||||
// if (!hasCardSaved) {
|
// if (!hasCardSaved) {
|
||||||
// explainerText += __('You must add a card to use this functionality.');
|
// explainerText += __('You must add a card to use this functionality.');
|
||||||
// }
|
// }
|
||||||
|
@ -187,22 +190,34 @@ function WalletSendTip(props: Props) {
|
||||||
const validTipInput = regexp.test(String(tipAmount));
|
const validTipInput = regexp.test(String(tipAmount));
|
||||||
let tipError;
|
let tipError;
|
||||||
|
|
||||||
if (!tipAmount) {
|
if (tipAmount === 0) {
|
||||||
tipError = __('Amount must be a number');
|
|
||||||
} else if (tipAmount <= 0) {
|
|
||||||
tipError = __('Amount must be a positive number');
|
tipError = __('Amount must be a positive number');
|
||||||
} else if (tipAmount < MINIMUM_PUBLISH_BID) {
|
} else if (!tipAmount || typeof tipAmount !== 'number') {
|
||||||
tipError = __('Amount must be higher');
|
tipError = __('Amount must be a number');
|
||||||
} else if (!validTipInput) {
|
}
|
||||||
|
|
||||||
|
// if it's not fiat, aka it's boost or lbc tip
|
||||||
|
else if (activeTab !== TAB_FIAT) {
|
||||||
|
if (!validTipInput) {
|
||||||
tipError = __('Amount must have no more than 8 decimal places');
|
tipError = __('Amount must have no more than 8 decimal places');
|
||||||
} else if (tipAmount === balance) {
|
} else if (tipAmount === balance) {
|
||||||
tipError = __('Please decrease the amount to account for transaction fees');
|
tipError = __('Please decrease the amount to account for transaction fees');
|
||||||
} else if (tipAmount > balance) {
|
} else if (tipAmount > balance) {
|
||||||
tipError = __('Not enough Credits');
|
tipError = __('Not enough Credits');
|
||||||
|
} else if (tipAmount < MINIMUM_PUBLISH_BID) {
|
||||||
|
tipError = __('Amount must be higher');
|
||||||
|
}
|
||||||
|
// if tip fiat tab
|
||||||
|
} else {
|
||||||
|
if (tipAmount < 1) {
|
||||||
|
tipError = __('Amount must be at least one dollar');
|
||||||
|
} else if (tipAmount > 1000) {
|
||||||
|
tipError = __('Amount cannot be over 1000 dollars');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTipError(tipError);
|
setTipError(tipError);
|
||||||
}, [tipAmount, balance, setTipError]);
|
}, [tipAmount, balance, setTipError, activeTab]);
|
||||||
|
|
||||||
//
|
//
|
||||||
function sendSupportOrConfirm(instantTipMaxAmount = null) {
|
function sendSupportOrConfirm(instantTipMaxAmount = null) {
|
||||||
|
@ -267,11 +282,15 @@ function WalletSendTip(props: Props) {
|
||||||
tipChannelName,
|
tipChannelName,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log(customerTipResponse);
|
|
||||||
})
|
})
|
||||||
.catch(function(error) {
|
.catch(function(error) {
|
||||||
console.log(error);
|
var displayError = 'Sorry, there was an error in processing your payment!';
|
||||||
doToast({ message: error.message, isError: true });
|
|
||||||
|
if (error.message !== 'payment intent failed to confirm') {
|
||||||
|
displayError = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
doToast({ message: displayError, isError: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
|
@ -285,6 +304,7 @@ function WalletSendTip(props: Props) {
|
||||||
|
|
||||||
function handleCustomPriceChange(event: SyntheticInputEvent<*>) {
|
function handleCustomPriceChange(event: SyntheticInputEvent<*>) {
|
||||||
const tipAmount = parseFloat(event.target.value);
|
const tipAmount = parseFloat(event.target.value);
|
||||||
|
|
||||||
setCustomTipAmount(tipAmount);
|
setCustomTipAmount(tipAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,6 +388,8 @@ function WalletSendTip(props: Props) {
|
||||||
label={__('Tip')}
|
label={__('Tip')}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
var tipInputElement = document.getElementById('tip-input');
|
||||||
|
if (tipInputElement) { tipInputElement.focus() }
|
||||||
if (!isConfirming) {
|
if (!isConfirming) {
|
||||||
setActiveTab(TAB_LBC);
|
setActiveTab(TAB_LBC);
|
||||||
}
|
}
|
||||||
|
@ -382,6 +404,8 @@ function WalletSendTip(props: Props) {
|
||||||
label={__('Tip')}
|
label={__('Tip')}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
var tipInputElement = document.getElementById('tip-input');
|
||||||
|
if (tipInputElement) { tipInputElement.focus() }
|
||||||
if (!isConfirming) {
|
if (!isConfirming) {
|
||||||
setActiveTab(TAB_FIAT);
|
setActiveTab(TAB_FIAT);
|
||||||
}
|
}
|
||||||
|
@ -396,6 +420,8 @@ function WalletSendTip(props: Props) {
|
||||||
label={__('Boost')}
|
label={__('Boost')}
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
var tipInputElement = document.getElementById('tip-input');
|
||||||
|
if (tipInputElement) { tipInputElement.focus() }
|
||||||
if (!isConfirming) {
|
if (!isConfirming) {
|
||||||
setActiveTab(TAB_BOOST);
|
setActiveTab(TAB_BOOST);
|
||||||
}
|
}
|
||||||
|
@ -483,6 +509,7 @@ function WalletSendTip(props: Props) {
|
||||||
icon={iconToUse}
|
icon={iconToUse}
|
||||||
label={__('Custom')}
|
label={__('Custom')}
|
||||||
onClick={() => setUseCustomTip(true)}
|
onClick={() => setUseCustomTip(true)}
|
||||||
|
// disabled if it's receive fiat and there is no card or creator can't receive tips
|
||||||
disabled={activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip)}
|
disabled={activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectBalance } from 'lbry-redux';
|
import { makeSelectClaimForUri, selectBalance } from 'lbry-redux';
|
||||||
import WalletTipAmountSelector from './view';
|
import WalletTipAmountSelector from './view';
|
||||||
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
|
isAuthenticated: Boolean(selectUserVerifiedEmail(state)),
|
||||||
|
// claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
// claim: makeSelectClaimForUri(props.uri, false)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select)(WalletTipAmountSelector);
|
export default connect(select)(WalletTipAmountSelector);
|
||||||
|
|
|
@ -10,40 +10,145 @@ import I18nMessage from 'component/i18nMessage';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
|
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import { STRIPE_PUBLIC_KEY } from 'config';
|
||||||
|
|
||||||
|
let stripeEnvironment = 'test';
|
||||||
|
// if the key contains pk_live it's a live key
|
||||||
|
// update the environment for the calls to the backend to indicate which environment to hit
|
||||||
|
if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) {
|
||||||
|
stripeEnvironment = 'live';
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_TIP_AMOUNTS = [1, 5, 25, 100];
|
const DEFAULT_TIP_AMOUNTS = [1, 5, 25, 100];
|
||||||
|
|
||||||
|
const TAB_FIAT = 'TabFiat';
|
||||||
|
const TAB_LBC = 'TabLBC';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
balance: number,
|
balance: number,
|
||||||
amount: number,
|
amount: number,
|
||||||
onChange: (number) => void,
|
onChange: (number) => void,
|
||||||
|
isAuthenticated: boolean,
|
||||||
|
claim: StreamClaim,
|
||||||
|
uri: string,
|
||||||
|
onTipErrorChange: (string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function WalletTipAmountSelector(props: Props) {
|
function WalletTipAmountSelector(props: Props) {
|
||||||
const { balance, amount, onChange } = props;
|
const { balance, amount, onChange, activeTab, isAuthenticated, claim, uri, onTipErrorChange } = props;
|
||||||
const [useCustomTip, setUseCustomTip] = usePersistedState('comment-support:useCustomTip', false);
|
const [useCustomTip, setUseCustomTip] = usePersistedState('comment-support:useCustomTip', false);
|
||||||
const [tipError, setTipError] = React.useState();
|
const [tipError, setTipError] = React.useState();
|
||||||
|
|
||||||
|
const [canReceiveFiatTip, setCanReceiveFiatTip] = React.useState(); // dont persist because it needs to be calc'd per creator
|
||||||
|
const [hasCardSaved, setHasSavedCard] = usePersistedState('comment-support:hasCardSaved', false);
|
||||||
|
|
||||||
|
function shouldDisableAmountSelector(amount) {
|
||||||
|
return (
|
||||||
|
(amount > balance && activeTab !== TAB_FIAT) || (activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(activeTab);
|
||||||
|
|
||||||
|
console.log(claim);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if creator has a payment method saved
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
Lbryio.call(
|
||||||
|
'customer',
|
||||||
|
'status',
|
||||||
|
{
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
).then((customerStatusResponse) => {
|
||||||
|
const defaultPaymentMethodId =
|
||||||
|
customerStatusResponse.Customer &&
|
||||||
|
customerStatusResponse.Customer.invoice_settings &&
|
||||||
|
customerStatusResponse.Customer.invoice_settings.default_payment_method &&
|
||||||
|
customerStatusResponse.Customer.invoice_settings.default_payment_method.id;
|
||||||
|
|
||||||
|
console.log('here');
|
||||||
|
console.log(defaultPaymentMethodId);
|
||||||
|
|
||||||
|
setHasSavedCard(Boolean(defaultPaymentMethodId));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//
|
||||||
|
React.useEffect(() => {
|
||||||
|
Lbryio.call(
|
||||||
|
'account',
|
||||||
|
'check',
|
||||||
|
{
|
||||||
|
channel_claim_id: channelClaimId,
|
||||||
|
channel_name: tipChannelName,
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
)
|
||||||
|
.then((accountCheckResponse) => {
|
||||||
|
if (accountCheckResponse === true && canReceiveFiatTip !== true) {
|
||||||
|
setCanReceiveFiatTip(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
// console.log(error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
|
||||||
|
// setHasSavedCard(false);
|
||||||
|
// setCanReceiveFiatTip(true);
|
||||||
|
|
||||||
const regexp = RegExp(/^(\d*([.]\d{0,8})?)$/);
|
const regexp = RegExp(/^(\d*([.]\d{0,8})?)$/);
|
||||||
const validTipInput = regexp.test(String(amount));
|
const validTipInput = regexp.test(String(amount));
|
||||||
let tipError;
|
let tipError;
|
||||||
|
|
||||||
if (!amount) {
|
if (amount === 0) {
|
||||||
tipError = __('Amount must be a number');
|
|
||||||
} else if (amount <= 0) {
|
|
||||||
tipError = __('Amount must be a positive number');
|
tipError = __('Amount must be a positive number');
|
||||||
} else if (amount < MINIMUM_PUBLISH_BID) {
|
} else if (!amount || typeof amount !== 'number') {
|
||||||
tipError = __('Amount must be higher');
|
tipError = __('Amount must be a number');
|
||||||
} else if (!validTipInput) {
|
}
|
||||||
|
|
||||||
|
// if it's not fiat, aka it's boost or lbc tip
|
||||||
|
else if (activeTab !== TAB_FIAT) {
|
||||||
|
if (!validTipInput) {
|
||||||
tipError = __('Amount must have no more than 8 decimal places');
|
tipError = __('Amount must have no more than 8 decimal places');
|
||||||
} else if (amount === balance) {
|
} else if (amount === balance) {
|
||||||
tipError = __('Please decrease the amount to account for transaction fees');
|
tipError = __('Please decrease the amount to account for transaction fees');
|
||||||
} else if (amount > balance) {
|
} else if (amount > balance) {
|
||||||
tipError = __('Not enough Credits');
|
tipError = __('Not enough Credits');
|
||||||
|
} else if (amount < MINIMUM_PUBLISH_BID) {
|
||||||
|
tipError = __('Amount must be higher');
|
||||||
}
|
}
|
||||||
|
// if tip fiat tab
|
||||||
|
} else {
|
||||||
|
if (amount < 1) {
|
||||||
|
tipError = __('Amount must be at least one dollar');
|
||||||
|
} else if (amount > 1000) {
|
||||||
|
tipError = __('Amount cannot be over 1000 dollars');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTipError(tipError);
|
setTipError(tipError);
|
||||||
}, [amount, balance, setTipError]);
|
onTipErrorChange(tipError);
|
||||||
|
}, [amount, balance, setTipError, activeTab]);
|
||||||
|
|
||||||
function handleCustomPriceChange(amount: number) {
|
function handleCustomPriceChange(amount: number) {
|
||||||
const tipAmount = parseFloat(amount);
|
const tipAmount = parseFloat(amount);
|
||||||
|
@ -56,14 +161,14 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
{DEFAULT_TIP_AMOUNTS.map((defaultAmount) => (
|
{DEFAULT_TIP_AMOUNTS.map((defaultAmount) => (
|
||||||
<Button
|
<Button
|
||||||
key={defaultAmount}
|
key={defaultAmount}
|
||||||
disabled={amount > balance}
|
disabled={shouldDisableAmountSelector(defaultAmount)}
|
||||||
button="alt"
|
button="alt"
|
||||||
className={classnames('button-toggle button-toggle--expandformobile', {
|
className={classnames('button-toggle button-toggle--expandformobile', {
|
||||||
'button-toggle--active': defaultAmount === amount,
|
'button-toggle--active': defaultAmount === amount && !useCustomTip,
|
||||||
'button-toggle--disabled': amount > balance,
|
'button-toggle--disabled': amount > balance,
|
||||||
})}
|
})}
|
||||||
label={defaultAmount}
|
label={defaultAmount}
|
||||||
icon={ICONS.LBC}
|
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleCustomPriceChange(defaultAmount);
|
handleCustomPriceChange(defaultAmount);
|
||||||
setUseCustomTip(false);
|
setUseCustomTip(false);
|
||||||
|
@ -72,14 +177,15 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
))}
|
))}
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
|
disabled={activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip)}
|
||||||
className={classnames('button-toggle button-toggle--expandformobile', {
|
className={classnames('button-toggle button-toggle--expandformobile', {
|
||||||
'button-toggle--active': !DEFAULT_TIP_AMOUNTS.includes(amount),
|
'button-toggle--active': useCustomTip,
|
||||||
})}
|
})}
|
||||||
icon={ICONS.LBC}
|
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
|
||||||
label={__('Custom')}
|
label={__('Custom')}
|
||||||
onClick={() => setUseCustomTip(true)}
|
onClick={() => setUseCustomTip(true)}
|
||||||
/>
|
/>
|
||||||
{DEFAULT_TIP_AMOUNTS.some((val) => val > balance) && (
|
{activeTab === TAB_LBC && DEFAULT_TIP_AMOUNTS.some((val) => val > balance) && (
|
||||||
<Button
|
<Button
|
||||||
button="secondary"
|
button="secondary"
|
||||||
className="button-toggle-group-action"
|
className="button-toggle-group-action"
|
||||||
|
@ -90,18 +196,55 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{useCustomTip && activeTab === TAB_FIAT && !hasCardSaved &&
|
||||||
|
<>
|
||||||
|
<div className="help">
|
||||||
|
<span className="help--spendable">
|
||||||
|
<Button navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} label={__('Add a Card ')} button="link" /> To {__(' Tip Creators')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* has card saved but cant creator cant receive tips */}
|
||||||
|
{useCustomTip && activeTab === TAB_FIAT && hasCardSaved && !canReceiveFiatTip &&
|
||||||
|
<>
|
||||||
|
<div className="help">
|
||||||
|
<span className="help--spendable">Only select creators can receive tips at this time</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* has card saved but cant creator cant receive tips */}
|
||||||
|
{useCustomTip && activeTab === TAB_FIAT && hasCardSaved && canReceiveFiatTip &&
|
||||||
|
<>
|
||||||
|
<div className="help">
|
||||||
|
<span className="help--spendable">Send a tip directly from your attached card</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
{useCustomTip && (
|
{useCustomTip && (
|
||||||
<div className="comment__tip-input">
|
<div className="comment__tip-input">
|
||||||
<FormField
|
<FormField
|
||||||
autoFocus
|
autoFocus
|
||||||
name="tip-input"
|
name="tip-input"
|
||||||
label={
|
label={ activeTab === TAB_LBC ?
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{__('Custom support amount')}{' '}
|
{__('Custom support amount')}{' '}
|
||||||
<I18nMessage tokens={{ lbc_balance: <CreditAmount precision={4} amount={balance} /> }}>
|
<I18nMessage tokens={{ lbc_balance: <CreditAmount precision={4} amount={balance} /> }}>
|
||||||
(%lbc_balance% available)
|
(%lbc_balance% available)
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
// TODO: add conditional based on hasSavedCard
|
||||||
|
: <>
|
||||||
|
</>
|
||||||
|
|
||||||
|
// <>
|
||||||
|
// <div className="">
|
||||||
|
// <span className="help--spendable">Send a tip directly from your attached card</span>
|
||||||
|
// </div>
|
||||||
|
// </>
|
||||||
}
|
}
|
||||||
className="form-field--price-amount"
|
className="form-field--price-amount"
|
||||||
error={tipError}
|
error={tipError}
|
||||||
|
@ -115,7 +258,38 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!useCustomTip && <WalletSpendableBalanceHelp />}
|
{/*// TODO: add conditional based on hasSavedCard*/}
|
||||||
|
{/* lbc tab */}
|
||||||
|
{activeTab === TAB_LBC && <WalletSpendableBalanceHelp />}
|
||||||
|
{/* fiat button but no card saved */}
|
||||||
|
{!useCustomTip && activeTab === TAB_FIAT && !hasCardSaved &&
|
||||||
|
<>
|
||||||
|
<div className="help">
|
||||||
|
<span className="help--spendable">
|
||||||
|
<Button navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} label={__('Add a Card ')} button="link" /> To {__(' Tip Creators')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* has card saved but cant creator cant receive tips */}
|
||||||
|
{!useCustomTip && activeTab === TAB_FIAT && hasCardSaved && !canReceiveFiatTip &&
|
||||||
|
<>
|
||||||
|
<div className="help">
|
||||||
|
<span className="help--spendable">Only select creators can receive tips at this time</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* has card saved but cant creator cant receive tips */}
|
||||||
|
{!useCustomTip && activeTab === TAB_FIAT && hasCardSaved && canReceiveFiatTip &&
|
||||||
|
<>
|
||||||
|
<div className="help">
|
||||||
|
<span className="help--spendable">Send a tip directly from your attached card</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,3 +45,4 @@ export const VIEW_IMAGE = 'view_image';
|
||||||
export const CONFIRM_REMOVE_BTC_SWAP_ADDRESS = 'confirm_remove_btc_swap_address';
|
export const CONFIRM_REMOVE_BTC_SWAP_ADDRESS = 'confirm_remove_btc_swap_address';
|
||||||
export const COLLECTION_ADD = 'collection_add';
|
export const COLLECTION_ADD = 'collection_add';
|
||||||
export const COLLECTION_DELETE = 'collection_delete';
|
export const COLLECTION_DELETE = 'collection_delete';
|
||||||
|
export const CONFIRM_REMOVE_CARD = 'CONFIRM_REMOVE_CARD';
|
||||||
|
|
|
@ -39,6 +39,7 @@ exports.REPOST_NEW = 'repost';
|
||||||
exports.SEND = 'send';
|
exports.SEND = 'send';
|
||||||
exports.SETTINGS = 'settings';
|
exports.SETTINGS = 'settings';
|
||||||
exports.SETTINGS_STRIPE_CARD = 'settings/card';
|
exports.SETTINGS_STRIPE_CARD = 'settings/card';
|
||||||
|
exports.SETTINGS_STRIPE_ACCOUNT = 'settings/tip_account';
|
||||||
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
||||||
exports.SETTINGS_ADVANCED = 'settings/advanced';
|
exports.SETTINGS_ADVANCED = 'settings/advanced';
|
||||||
exports.SETTINGS_BLOCKED_MUTED = 'settings/block_and_mute';
|
exports.SETTINGS_BLOCKED_MUTED = 'settings/block_and_mute';
|
||||||
|
|
19
ui/modal/modalRemoveCard/index.js
Normal file
19
ui/modal/modalRemoveCard/index.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doHideModal } from 'redux/actions/app';
|
||||||
|
import { doAbandonTxo, doAbandonClaim, selectTransactionItems, doResolveUri } from 'lbry-redux';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
import ModalRevokeClaim from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
transactionItems: selectTransactionItems(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
toast: (message, isError) => dispatch(doToast({ message, isError })),
|
||||||
|
closeModal: () => dispatch(doHideModal()),
|
||||||
|
abandonTxo: (txo, cb) => dispatch(doAbandonTxo(txo, cb)),
|
||||||
|
abandonClaim: (txid, nout, cb) => dispatch(doAbandonClaim(txid, nout, cb)),
|
||||||
|
doResolveUri: (uri) => dispatch(doResolveUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(ModalRevokeClaim);
|
92
ui/modal/modalRemoveCard/view.jsx
Normal file
92
ui/modal/modalRemoveCard/view.jsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// @flow
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Modal } from 'modal/modal';
|
||||||
|
import { FormField } from 'component/common/form';
|
||||||
|
import * as txnTypes from 'constants/transaction_types';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import { STRIPE_PUBLIC_KEY } from 'config';
|
||||||
|
|
||||||
|
|
||||||
|
let stripeEnvironment = 'test';
|
||||||
|
// if the key contains pk_live it's a live key
|
||||||
|
// update the environment for the calls to the backend to indicate which environment to hit
|
||||||
|
if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) {
|
||||||
|
stripeEnvironment = 'live';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
closeModal: () => void,
|
||||||
|
abandonTxo: (Txo, () => void) => void,
|
||||||
|
abandonClaim: (string, number, ?() => void) => void,
|
||||||
|
tx: Txo,
|
||||||
|
claim: GenericClaim,
|
||||||
|
cb: () => void,
|
||||||
|
doResolveUri: (string) => void,
|
||||||
|
uri: string,
|
||||||
|
paymentMethodId: string,
|
||||||
|
setAsConfirmingCard: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ModalRevokeClaim(props: Props) {
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
console.log(that);
|
||||||
|
|
||||||
|
console.log(props);
|
||||||
|
|
||||||
|
const { closeModal, uri, paymentMethodId, setAsConfirmingCard } = props;
|
||||||
|
|
||||||
|
console.log(uri);
|
||||||
|
|
||||||
|
console.log(setAsConfirmingCard)
|
||||||
|
|
||||||
|
function removeCard(){
|
||||||
|
console.log(paymentMethodId);
|
||||||
|
|
||||||
|
Lbryio.call(
|
||||||
|
'customer',
|
||||||
|
'detach',
|
||||||
|
{
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
payment_method_id: paymentMethodId
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
).then((removeCardResponse) => {
|
||||||
|
console.log(removeCardResponse)
|
||||||
|
|
||||||
|
//TODO: add toast here
|
||||||
|
// closeModal();
|
||||||
|
|
||||||
|
location.reload();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal ariaHideApp={false} isOpen contentLabel={'hello'} type="card" onAborted={closeModal}>
|
||||||
|
<Card
|
||||||
|
title={'Confirm Remove Card'}
|
||||||
|
// body={getMsgBody(type, isSupport, name)}
|
||||||
|
actions={
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button
|
||||||
|
className="stripe__confirm-remove-card"
|
||||||
|
button="secondary"
|
||||||
|
icon={ICONS.DELETE}
|
||||||
|
label={'Remove Card'}
|
||||||
|
onClick={removeCard}
|
||||||
|
/>
|
||||||
|
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ const ModalPhoneCollection = lazyImport(() => import('modal/modalPhoneCollection
|
||||||
const ModalPublish = lazyImport(() => import('modal/modalPublish' /* webpackChunkName: "modalPublish" */));
|
const ModalPublish = lazyImport(() => import('modal/modalPublish' /* webpackChunkName: "modalPublish" */));
|
||||||
const ModalPublishPreview = lazyImport(() => import('modal/modalPublishPreview' /* webpackChunkName: "modalPublishPreview" */));
|
const ModalPublishPreview = lazyImport(() => import('modal/modalPublishPreview' /* webpackChunkName: "modalPublishPreview" */));
|
||||||
const ModalRemoveBtcSwapAddress = lazyImport(() => import('modal/modalRemoveBtcSwapAddress' /* webpackChunkName: "modalRemoveBtcSwapAddress" */));
|
const ModalRemoveBtcSwapAddress = lazyImport(() => import('modal/modalRemoveBtcSwapAddress' /* webpackChunkName: "modalRemoveBtcSwapAddress" */));
|
||||||
|
const ModalRemoveCard = lazyImport(() => import('modal/modalRemoveCard' /* webpackChunkName: "modalRemoveCard" */));
|
||||||
const ModalRemoveFile = lazyImport(() => import('modal/modalRemoveFile' /* webpackChunkName: "modalRemoveFile" */));
|
const ModalRemoveFile = lazyImport(() => import('modal/modalRemoveFile' /* webpackChunkName: "modalRemoveFile" */));
|
||||||
const ModalRevokeClaim = lazyImport(() => import('modal/modalRevokeClaim' /* webpackChunkName: "modalRevokeClaim" */));
|
const ModalRevokeClaim = lazyImport(() => import('modal/modalRevokeClaim' /* webpackChunkName: "modalRevokeClaim" */));
|
||||||
const ModalRewardCode = lazyImport(() => import('modal/modalRewardCode' /* webpackChunkName: "modalRewardCode" */));
|
const ModalRewardCode = lazyImport(() => import('modal/modalRewardCode' /* webpackChunkName: "modalRewardCode" */));
|
||||||
|
@ -151,6 +152,8 @@ function ModalRouter(props: Props) {
|
||||||
return ModalClaimCollectionAdd;
|
return ModalClaimCollectionAdd;
|
||||||
case MODALS.COLLECTION_DELETE:
|
case MODALS.COLLECTION_DELETE:
|
||||||
return ModalDeleteCollection;
|
return ModalDeleteCollection;
|
||||||
|
case MODALS.CONFIRM_REMOVE_CARD:
|
||||||
|
return ModalRemoveCard;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
} from 'redux/selectors/settings';
|
} from 'redux/selectors/settings';
|
||||||
import { doWalletStatus, selectMyChannelUrls, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
|
import { doWalletStatus, selectMyChannelUrls, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
|
||||||
import SettingsPage from './view';
|
import SettingsPage from './view';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
daemonSettings: selectDaemonSettings(state),
|
daemonSettings: selectDaemonSettings(state),
|
||||||
|
@ -38,6 +38,7 @@ const select = (state) => ({
|
||||||
darkModeTimes: makeSelectClientSetting(SETTINGS.DARK_MODE_TIMES)(state),
|
darkModeTimes: makeSelectClientSetting(SETTINGS.DARK_MODE_TIMES)(state),
|
||||||
language: selectLanguage(state),
|
language: selectLanguage(state),
|
||||||
myChannelUrls: selectMyChannelUrls(state),
|
myChannelUrls: selectMyChannelUrls(state),
|
||||||
|
user: selectUser(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -72,6 +72,7 @@ type Props = {
|
||||||
enterSettings: () => void,
|
enterSettings: () => void,
|
||||||
exitSettings: () => void,
|
exitSettings: () => void,
|
||||||
myChannelUrls: ?Array<string>,
|
myChannelUrls: ?Array<string>,
|
||||||
|
user: User,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -189,6 +190,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
clearCache,
|
clearCache,
|
||||||
openModal,
|
openModal,
|
||||||
myChannelUrls,
|
myChannelUrls,
|
||||||
|
user,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { storedPassword } = this.state;
|
const { storedPassword } = this.state;
|
||||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||||
|
@ -206,14 +208,32 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
className="card-stack"
|
className="card-stack"
|
||||||
>
|
>
|
||||||
{/* @if TARGET='web' */}
|
{/* @if TARGET='web' */}
|
||||||
<Card
|
{user && user.fiat_enabled && <Card
|
||||||
title={__('Add card to tip creators in USD')}
|
title={__('Bank Accounts')}
|
||||||
|
subtitle={__('Connect a bank account to receive tips and compensation in your local currency')}
|
||||||
actions={
|
actions={
|
||||||
<div className="section__actions">
|
<div className="section__actions">
|
||||||
<Button
|
<Button
|
||||||
button="secondary"
|
button="secondary"
|
||||||
label={__('Manage Card')}
|
label={__('Manage')}
|
||||||
icon={ICONS.WALLET}
|
icon={ICONS.SETTINGS}
|
||||||
|
navigate={`/$/${PAGES.SETTINGS_STRIPE_ACCOUNT}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>}
|
||||||
|
{/* @endif */}
|
||||||
|
|
||||||
|
{/* @if TARGET='web' */}
|
||||||
|
<Card
|
||||||
|
title={__('Payment Methods')}
|
||||||
|
subtitle={__('Add a credit card to tip creators in their local currency')}
|
||||||
|
actions={
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button
|
||||||
|
button="secondary"
|
||||||
|
label={__('Manage')}
|
||||||
|
icon={ICONS.SETTINGS}
|
||||||
navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`}
|
navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
13
ui/page/settingsStripeAccount/index.js
Normal file
13
ui/page/settingsStripeAccount/index.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
import StripeAccountConnection from './view';
|
||||||
|
import { selectUser } from 'redux/selectors/user';
|
||||||
|
|
||||||
|
// function that receives state parameter and returns object of functions that accept state
|
||||||
|
const select = (state) => ({
|
||||||
|
user: selectUser(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = (dispatch) => ({});
|
||||||
|
|
||||||
|
export default withRouter(connect(select, perform)(StripeAccountConnection));
|
344
ui/page/settingsStripeAccount/view.jsx
Normal file
344
ui/page/settingsStripeAccount/view.jsx
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
// @flow
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import Card from 'component/common/card';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import { URL, WEBPACK_WEB_PORT, STRIPE_PUBLIC_KEY } from 'config';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
let stripeEnvironment = 'test';
|
||||||
|
// if the key contains pk_live it's a live key
|
||||||
|
// update the environment for the calls to the backend to indicate which environment to hit
|
||||||
|
if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) {
|
||||||
|
stripeEnvironment = 'live';
|
||||||
|
}
|
||||||
|
|
||||||
|
let successStripeRedirectUrl, failureStripeRedirectUrl;
|
||||||
|
let successEndpoint = '/$/settings/tip_account';
|
||||||
|
let failureEndpoint = '/$/settings/tip_account';
|
||||||
|
if (isDev) {
|
||||||
|
successStripeRedirectUrl = 'http://localhost:' + WEBPACK_WEB_PORT + successEndpoint;
|
||||||
|
failureStripeRedirectUrl = 'http://localhost:' + WEBPACK_WEB_PORT + failureEndpoint;
|
||||||
|
} else {
|
||||||
|
successStripeRedirectUrl = URL + successEndpoint;
|
||||||
|
failureStripeRedirectUrl = URL + failureEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
source: string,
|
||||||
|
user: User,
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
error: boolean,
|
||||||
|
loading: boolean,
|
||||||
|
content: ?string,
|
||||||
|
stripeConnectionUrl: string,
|
||||||
|
// alreadyUpdated: boolean,
|
||||||
|
accountConfirmed: boolean,
|
||||||
|
accountPendingConfirmation: boolean,
|
||||||
|
accountNotConfirmedButReceivedTips: boolean,
|
||||||
|
unpaidBalance: number,
|
||||||
|
pageTitle: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
class StripeAccountConnection extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
error: false,
|
||||||
|
content: null,
|
||||||
|
loading: true,
|
||||||
|
accountConfirmed: false,
|
||||||
|
accountPendingConfirmation: false,
|
||||||
|
accountNotConfirmedButReceivedTips: false,
|
||||||
|
unpaidBalance: 0,
|
||||||
|
stripeConnectionUrl: '',
|
||||||
|
pageTitle: 'Add Payout Method',
|
||||||
|
accountTransactions: [],
|
||||||
|
// alreadyUpdated: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { user } = this.props;
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
this.experimentalUiEnabled = user && user.experimental_ui;
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
function getAndSetAccountLink(stillNeedToConfirmAccount) {
|
||||||
|
Lbryio.call(
|
||||||
|
'account',
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
return_url: successStripeRedirectUrl,
|
||||||
|
refresh_url: failureStripeRedirectUrl,
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
).then((accountLinkResponse) => {
|
||||||
|
// stripe link for user to navigate to and confirm account
|
||||||
|
const stripeConnectionUrl = accountLinkResponse.url;
|
||||||
|
|
||||||
|
// set connection url on frontend
|
||||||
|
that.setState({
|
||||||
|
stripeConnectionUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// show the account confirmation link if not created already
|
||||||
|
if (stillNeedToConfirmAccount) {
|
||||||
|
that.setState({
|
||||||
|
accountPendingConfirmation: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the account status endpoint
|
||||||
|
Lbryio.call(
|
||||||
|
'account',
|
||||||
|
'status',
|
||||||
|
{
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
)
|
||||||
|
.then((accountStatusResponse) => {
|
||||||
|
const yetToBeCashedOutBalance = accountStatusResponse.total_received_unpaid;
|
||||||
|
if (yetToBeCashedOutBalance) {
|
||||||
|
that.setState({
|
||||||
|
unpaidBalance: yetToBeCashedOutBalance,
|
||||||
|
});
|
||||||
|
|
||||||
|
Lbryio.call(
|
||||||
|
'account',
|
||||||
|
'list',
|
||||||
|
{
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
)
|
||||||
|
.then((accountListResponse) => {
|
||||||
|
|
||||||
|
that.setState({
|
||||||
|
accountTransactions: accountListResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(accountListResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if charges already enabled, no need to generate an account link
|
||||||
|
if (accountStatusResponse.charges_enabled) {
|
||||||
|
// account has already been confirmed
|
||||||
|
|
||||||
|
that.setState({
|
||||||
|
accountConfirmed: true,
|
||||||
|
});
|
||||||
|
// user has not confirmed an account but have received payments
|
||||||
|
} else if (accountStatusResponse.total_received_unpaid > 0) {
|
||||||
|
that.setState({
|
||||||
|
accountNotConfirmedButReceivedTips: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
getAndSetAccountLink();
|
||||||
|
|
||||||
|
// user has not received any amount or confirmed an account
|
||||||
|
} else {
|
||||||
|
// get stripe link and set it on the frontend
|
||||||
|
// pass true so it updates the frontend
|
||||||
|
getAndSetAccountLink(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
// errorString passed from the API (with a 403 error)
|
||||||
|
const errorString = 'account not linked to user, please link first';
|
||||||
|
|
||||||
|
// if it's beamer's error indicating the account is not linked yet
|
||||||
|
if (error.message.indexOf(errorString) > -1) {
|
||||||
|
// get stripe link and set it on the frontend
|
||||||
|
getAndSetAccountLink();
|
||||||
|
} else {
|
||||||
|
// not an error from Beamer, throw it
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
stripeConnectionUrl,
|
||||||
|
accountConfirmed,
|
||||||
|
accountPendingConfirmation,
|
||||||
|
unpaidBalance,
|
||||||
|
accountNotConfirmedButReceivedTips,
|
||||||
|
pageTitle,
|
||||||
|
accountTransactions,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const { user } = this.props;
|
||||||
|
|
||||||
|
if (user.fiat_enabled) {
|
||||||
|
return (
|
||||||
|
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
|
||||||
|
<Card
|
||||||
|
title={<div className="table__header-text">{__('Connect a bank account')}</div>}
|
||||||
|
isBodyList
|
||||||
|
body={
|
||||||
|
<div>
|
||||||
|
{/* show while waiting for account status */}
|
||||||
|
{!accountConfirmed && !accountPendingConfirmation && !accountNotConfirmedButReceivedTips && (
|
||||||
|
<div className="card__body-actions">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h3>{__('Getting your bank account connection status...')}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* user has yet to complete their integration */}
|
||||||
|
{!accountConfirmed && accountPendingConfirmation && (
|
||||||
|
<div className="card__body-actions">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h3>{__('Connect your bank account to Odysee to receive donations directly from users')}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="section__actions">
|
||||||
|
<a href={stripeConnectionUrl}>
|
||||||
|
<Button button="secondary" label={__('Connect your bank account')} icon={ICONS.FINANCE} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* user has completed their integration */}
|
||||||
|
{accountConfirmed && (
|
||||||
|
<div className="card__body-actions">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h3>{__('Congratulations! Your account has been connected with Odysee.')}</h3>
|
||||||
|
{unpaidBalance > 0 ? (
|
||||||
|
<div>
|
||||||
|
<br />
|
||||||
|
<h3>
|
||||||
|
{__(
|
||||||
|
'Your pending account balance is $%balance% USD.',
|
||||||
|
{ balance: unpaidBalance / 100 }
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<br />
|
||||||
|
<h3>{__('Your account balance is $0 USD. When you receive a tip you will see it here.')}</h3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{accountNotConfirmedButReceivedTips && (
|
||||||
|
<div className="card__body-actions">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h3>{__('Congratulations, you have already begun receiving tips on Odysee!')}</h3>
|
||||||
|
<div>
|
||||||
|
<br />
|
||||||
|
<h3>
|
||||||
|
{__(
|
||||||
|
'Your pending account balance is $%balance% USD.',
|
||||||
|
{ balance: unpaidBalance / 100 }
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<h3>
|
||||||
|
{__('Connect your bank account to be able to cash your pending balance out to your account.')}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="section__actions">
|
||||||
|
<a href={stripeConnectionUrl}>
|
||||||
|
<Button button="secondary" label={__('Connect your bank account')} icon={ICONS.FINANCE} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
|
||||||
|
{/* customer already has transactions */}
|
||||||
|
{accountTransactions && accountTransactions.length > 0 && (
|
||||||
|
|
||||||
|
<Card
|
||||||
|
title={__('Tip History')}
|
||||||
|
body={
|
||||||
|
<>
|
||||||
|
<div className="table__wrapper">
|
||||||
|
<table className="table table--transactions">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="date-header">{__('Date')}</th>
|
||||||
|
<th>{<>{__('Receiving Channel Name')}</>}</th>
|
||||||
|
<th>{__('Tip Location')}</th>
|
||||||
|
<th>{__('Amount (USD)')} </th>
|
||||||
|
<th>{__('Processing Fee')}</th>
|
||||||
|
<th>{__('Odysee Fee')}</th>
|
||||||
|
<th>{__('Received Amount')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{accountTransactions &&
|
||||||
|
accountTransactions.reverse().map((transaction) => (
|
||||||
|
<tr key={transaction.name + transaction.created_at}>
|
||||||
|
<td>{moment(transaction.created_at).format('LLL')}</td>
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
className="stripe__card-link-text"
|
||||||
|
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||||
|
label={transaction.channel_name}
|
||||||
|
button="link"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
className="stripe__card-link-text"
|
||||||
|
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||||
|
label={transaction.channel_claim_id === transaction.source_claim_id ? 'Channel Page' : 'File Page'}
|
||||||
|
button="link"
|
||||||
|
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>${transaction.tipped_amount / 100}</td>
|
||||||
|
<td>${transaction.transaction_fee / 100}</td>
|
||||||
|
<td>${transaction.application_fee / 100}</td>
|
||||||
|
<td>${transaction.received_amount / 100}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <></>; // probably null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StripeAccountConnection;
|
|
@ -2,6 +2,8 @@ import { connect } from 'react-redux';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
|
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||||
import { selectUserVerifiedEmail, selectUserEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail, selectUserEmail } from 'redux/selectors/user';
|
||||||
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
|
||||||
import SettingsStripeCard from './view';
|
import SettingsStripeCard from './view';
|
||||||
|
|
||||||
|
@ -13,6 +15,9 @@ const select = (state) => ({
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
|
doOpenModal,
|
||||||
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
|
doToast: (options) => dispatch(doToast(options)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(SettingsStripeCard);
|
export default connect(select, perform)(SettingsStripeCard);
|
||||||
|
|
|
@ -7,10 +7,10 @@ import Card from 'component/common/card';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
import { STRIPE_PUBLIC_KEY } from 'config';
|
import { STRIPE_PUBLIC_KEY } from 'config';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import Plastic from 'react-plastic';
|
||||||
let scriptLoading = false;
|
import Button from 'component/button';
|
||||||
// let scriptLoaded = false;
|
import * as ICONS from 'constants/icons';
|
||||||
// let scriptDidError = false; // these could probably be in state if managing locally
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
|
||||||
let stripeEnvironment = 'test';
|
let stripeEnvironment = 'test';
|
||||||
// if the key contains pk_live it's a live key
|
// if the key contains pk_live it's a live key
|
||||||
|
@ -19,12 +19,16 @@ if (STRIPE_PUBLIC_KEY.indexOf('pk_live') > -1) {
|
||||||
stripeEnvironment = 'live';
|
stripeEnvironment = 'live';
|
||||||
}
|
}
|
||||||
|
|
||||||
// type Props = {
|
// eslint-disable-next-line flowtype/no-types-missing-file-annotation
|
||||||
// disabled: boolean,
|
type Props = {
|
||||||
// label: ?string,
|
disabled: boolean,
|
||||||
// email: ?string,
|
label: ?string,
|
||||||
// scriptFailedToLoad: boolean,
|
email: ?string,
|
||||||
// };
|
scriptFailedToLoad: boolean,
|
||||||
|
doOpenModal: (string, {}) => void,
|
||||||
|
openModal: (string, {}) => void,
|
||||||
|
setAsConfirmingCard: () => void,
|
||||||
|
};
|
||||||
//
|
//
|
||||||
// type State = {
|
// type State = {
|
||||||
// open: boolean,
|
// open: boolean,
|
||||||
|
@ -46,12 +50,17 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
customerTransactions: [],
|
customerTransactions: [],
|
||||||
pageTitle: 'Add Card',
|
pageTitle: 'Add Card',
|
||||||
userCardDetails: {},
|
userCardDetails: {},
|
||||||
|
paymentMethodId: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
var that = this;
|
var that = this;
|
||||||
|
|
||||||
|
console.log(this.props);
|
||||||
|
|
||||||
|
var doToast = this.props.doToast;
|
||||||
|
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = 'https://js.stripe.com/v3/';
|
script.src = 'https://js.stripe.com/v3/';
|
||||||
script.async = true;
|
script.async = true;
|
||||||
|
@ -86,33 +95,27 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
if (userHasAlreadySetupPayment) {
|
if (userHasAlreadySetupPayment) {
|
||||||
var card = customerStatusResponse.PaymentMethods[0].card;
|
var card = customerStatusResponse.PaymentMethods[0].card;
|
||||||
|
|
||||||
|
var customer = customerStatusResponse.Customer;
|
||||||
|
|
||||||
|
var topOfDisplay = customer.email.split('@')[0];
|
||||||
|
var bottomOfDisplay = '@' + customer.email.split('@')[1];
|
||||||
|
|
||||||
|
console.log(customerStatusResponse.Customer);
|
||||||
|
|
||||||
var cardDetails = {
|
var cardDetails = {
|
||||||
brand: card.brand,
|
brand: card.brand,
|
||||||
expiryYear: card.exp_year,
|
expiryYear: card.exp_year,
|
||||||
expiryMonth: card.exp_month,
|
expiryMonth: card.exp_month,
|
||||||
lastFour: card.last4,
|
lastFour: card.last4,
|
||||||
|
topOfDisplay: topOfDisplay,
|
||||||
|
bottomOfDisplay: bottomOfDisplay,
|
||||||
};
|
};
|
||||||
|
|
||||||
that.setState({
|
that.setState({
|
||||||
currentFlowStage: 'cardConfirmed',
|
currentFlowStage: 'cardConfirmed',
|
||||||
pageTitle: 'Tip History',
|
pageTitle: 'Tip History',
|
||||||
userCardDetails: cardDetails,
|
userCardDetails: cardDetails,
|
||||||
});
|
paymentMethodId: customerStatusResponse.PaymentMethods[0].id,
|
||||||
|
|
||||||
// get customer transactions
|
|
||||||
Lbryio.call(
|
|
||||||
'customer',
|
|
||||||
'list',
|
|
||||||
{
|
|
||||||
environment: stripeEnvironment,
|
|
||||||
},
|
|
||||||
'post'
|
|
||||||
).then((customerTransactionsResponse) => {
|
|
||||||
that.setState({
|
|
||||||
customerTransactions: customerTransactionsResponse,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(customerTransactionsResponse);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// otherwise, prompt them to save a card
|
// otherwise, prompt them to save a card
|
||||||
|
@ -138,6 +141,22 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
setupStripe();
|
setupStripe();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get customer transactions
|
||||||
|
Lbryio.call(
|
||||||
|
'customer',
|
||||||
|
'list',
|
||||||
|
{
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
).then((customerTransactionsResponse) => {
|
||||||
|
that.setState({
|
||||||
|
customerTransactions: customerTransactionsResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(customerTransactionsResponse);
|
||||||
|
});
|
||||||
// if the status call fails, either an actual error or need to run setup first
|
// if the status call fails, either an actual error or need to run setup first
|
||||||
})
|
})
|
||||||
.catch(function(error) {
|
.catch(function(error) {
|
||||||
|
@ -147,7 +166,7 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
const errorString = 'user as customer is not setup yet';
|
const errorString = 'user as customer is not setup yet';
|
||||||
|
|
||||||
// if it's beamer's error indicating the account is not linked yet
|
// if it's beamer's error indicating the account is not linked yet
|
||||||
if (error.message.indexOf(errorString) > -1) {
|
if (error.message && error.message.indexOf(errorString) > -1) {
|
||||||
// send them to save a card
|
// send them to save a card
|
||||||
that.setState({
|
that.setState({
|
||||||
currentFlowStage: 'confirmingCard',
|
currentFlowStage: 'confirmingCard',
|
||||||
|
@ -169,12 +188,16 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
// instantiate stripe elements
|
// instantiate stripe elements
|
||||||
setupStripe();
|
setupStripe();
|
||||||
});
|
});
|
||||||
|
} else if (error === 'internal_apis_down') {
|
||||||
|
var displayString = 'There was an error from the server, please let support know'
|
||||||
|
doToast({ message: displayString, isError: true });
|
||||||
} else {
|
} else {
|
||||||
console.log('Unseen before error');
|
console.log('Unseen before error');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
|
|
||||||
function setupStripe() {
|
function setupStripe() {
|
||||||
// TODO: have to fix this, using so that the script is available
|
// TODO: have to fix this, using so that the script is available
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
@ -302,17 +325,25 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
).then((customerStatusResponse) => {
|
).then((customerStatusResponse) => {
|
||||||
var card = customerStatusResponse.PaymentMethods[0].card;
|
var card = customerStatusResponse.PaymentMethods[0].card;
|
||||||
|
|
||||||
|
var customer = customerStatusResponse.Customer;
|
||||||
|
|
||||||
|
var topOfDisplay = customer.email.split('@')[0];
|
||||||
|
var bottomOfDisplay = '@' + customer.email.split('@')[1];
|
||||||
|
|
||||||
var cardDetails = {
|
var cardDetails = {
|
||||||
brand: card.brand,
|
brand: card.brand,
|
||||||
expiryYear: card.exp_year,
|
expiryYear: card.exp_year,
|
||||||
expiryMonth: card.exp_month,
|
expiryMonth: card.exp_month,
|
||||||
lastFour: card.last4,
|
lastFour: card.last4,
|
||||||
|
topOfDisplay,
|
||||||
|
bottomOfDisplay,
|
||||||
};
|
};
|
||||||
|
|
||||||
that.setState({
|
that.setState({
|
||||||
currentFlowStage: 'cardConfirmed',
|
currentFlowStage: 'cardConfirmed',
|
||||||
pageTitle: 'Tip History',
|
pageTitle: 'Tip History',
|
||||||
userCardDetails: cardDetails,
|
userCardDetails: cardDetails,
|
||||||
|
paymentMethodId: customerStatusResponse.PaymentMethods[0].id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -325,59 +356,19 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
if (!scriptLoading) {
|
|
||||||
this.updateStripeHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
// pretty sure this doesn't exist
|
|
||||||
// $FlowFixMe
|
|
||||||
if (this.loadPromise) {
|
|
||||||
// $FlowFixMe
|
|
||||||
this.loadPromise.reject();
|
|
||||||
}
|
|
||||||
// pretty sure this doesn't exist
|
|
||||||
// $FlowFixMe
|
|
||||||
if (CardVerify.stripeHandler && this.state.open) {
|
|
||||||
// $FlowFixMe
|
|
||||||
CardVerify.stripeHandler.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onScriptLoaded = () => {
|
|
||||||
// if (!CardVerify.stripeHandler) {
|
|
||||||
// CardVerify.stripeHandler = StripeCheckout.configure({
|
|
||||||
// key: 'pk_test_NoL1JWL7i1ipfhVId5KfDZgo',
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// if (this.hasPendingClick) {
|
|
||||||
// this.showStripeDialog();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
|
|
||||||
onScriptError = (...args) => {
|
|
||||||
this.setState({ scriptFailedToLoad: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onClosed = () => {
|
|
||||||
this.setState({ open: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
updateStripeHandler() {
|
|
||||||
// if (!CardVerify.stripeHandler) {
|
|
||||||
// CardVerify.stripeHandler = StripeCheckout.configure({
|
|
||||||
// key: this.props.stripeKey,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { scriptFailedToLoad } = this.props;
|
var that = this;
|
||||||
|
|
||||||
const { currentFlowStage, customerTransactions, pageTitle, userCardDetails } = this.state;
|
function setAsConfirmingCard(){
|
||||||
|
that.setState({
|
||||||
|
currentFlowStage: 'confirmingCard',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { scriptFailedToLoad, doOpenModal, openModal } = this.props;
|
||||||
|
|
||||||
|
const { currentFlowStage, customerTransactions, pageTitle, userCardDetails, paymentMethodId } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
|
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
|
||||||
|
@ -393,6 +384,7 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* customer has not added a card yet */}
|
||||||
{currentFlowStage === 'confirmingCard' && (
|
{currentFlowStage === 'confirmingCard' && (
|
||||||
<div className="sr-root">
|
<div className="sr-root">
|
||||||
<div className="sr-main">
|
<div className="sr-main">
|
||||||
|
@ -411,21 +403,39 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* if the user has already confirmed their card */}
|
||||||
{currentFlowStage === 'cardConfirmed' && (
|
{currentFlowStage === 'cardConfirmed' && (
|
||||||
<div className="successCard">
|
<div className="successCard">
|
||||||
<Card
|
<Card
|
||||||
title={__('Card Details')}
|
title={__('Card Details')}
|
||||||
body={
|
body={
|
||||||
<>
|
<>
|
||||||
<h4 className="grey-text">
|
<Plastic
|
||||||
Brand: {userCardDetails.brand.toUpperCase()} Last 4: {userCardDetails.lastFour}
|
type={userCardDetails.brand}
|
||||||
Expires: {userCardDetails.expiryMonth}/{userCardDetails.expiryYear}
|
name={userCardDetails.topOfDisplay + ' ' + userCardDetails.bottomOfDisplay}
|
||||||
</h4>
|
expiry={userCardDetails.expiryMonth + '/' + userCardDetails.expiryYear}
|
||||||
|
number={'____________' + userCardDetails.lastFour}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
button="secondary"
|
||||||
|
label={__('Remove Card')}
|
||||||
|
icon={ICONS.DELETE}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
openModal(MODALS.CONFIRM_REMOVE_CARD, {
|
||||||
|
paymentMethodId: paymentMethodId,
|
||||||
|
setAsConfirmingCard: setAsConfirmingCard,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
{/* if a user has no transactions yet */}
|
||||||
{(!customerTransactions || customerTransactions.length === 0) && (
|
{(!customerTransactions || customerTransactions.length === 0) && (
|
||||||
<Card
|
<Card
|
||||||
title={__('Tip History')}
|
title={__('Tip History')}
|
||||||
|
@ -433,6 +443,10 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* customer already has transactions */}
|
||||||
{customerTransactions && customerTransactions.length > 0 && (
|
{customerTransactions && customerTransactions.length > 0 && (
|
||||||
<Card
|
<Card
|
||||||
title={__('Tip History')}
|
title={__('Tip History')}
|
||||||
|
@ -444,16 +458,33 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
<tr>
|
<tr>
|
||||||
<th className="date-header">{__('Date')}</th>
|
<th className="date-header">{__('Date')}</th>
|
||||||
<th>{<>{__('Receiving Channel Name')}</>}</th>
|
<th>{<>{__('Receiving Channel Name')}</>}</th>
|
||||||
|
<th>{__('Tip Location')}</th>
|
||||||
<th>{__('Amount (USD)')} </th>
|
<th>{__('Amount (USD)')} </th>
|
||||||
<th>{__('Anonymous')}</th>
|
<th>{__('Anonymous')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{customerTransactions &&
|
{customerTransactions &&
|
||||||
customerTransactions.map((transaction) => (
|
customerTransactions.reverse().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>{transaction.channel_name}</td>
|
<td>
|
||||||
|
<Button
|
||||||
|
className="stripe__card-link-text"
|
||||||
|
navigate={'/' + transaction.channel_name + ':' + transaction.channel_claim_id}
|
||||||
|
label={transaction.channel_name}
|
||||||
|
button="link"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
className="stripe__card-link-text"
|
||||||
|
navigate={'/' + transaction.channel_name + ':' + transaction.source_claim_id}
|
||||||
|
label={transaction.channel_claim_id === transaction.source_claim_id ? 'Channel Page' : 'File Page'}
|
||||||
|
button="link"
|
||||||
|
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>${transaction.tipped_amount / 100}</td>
|
<td>${transaction.tipped_amount / 100}</td>
|
||||||
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
|
<td>{transaction.private_tip ? 'Yes' : 'No'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -465,8 +496,7 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,6 @@ const WalletPage = (props: Props) => {
|
||||||
) : (
|
) : (
|
||||||
<div className="card-stack">
|
<div className="card-stack">
|
||||||
<WalletBalance />
|
<WalletBalance />
|
||||||
{/* @if TARGET='web' */}
|
|
||||||
<StripeAccountConnection />
|
|
||||||
{/* @endif */}
|
|
||||||
<TxoList search={search} />
|
<TxoList search={search} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -370,16 +370,32 @@ export function doCommentReact(commentId: string, type: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param comment
|
||||||
|
* @param claim_id - File claim id
|
||||||
|
* @param parent_id - What is this?
|
||||||
|
* @param uri
|
||||||
|
* @param livestream
|
||||||
|
* @param {string} [txid] Optional transaction id
|
||||||
|
* @param {string} [payment_intent_id] Optional transaction id
|
||||||
|
* @param {string} [environment] Optional environment for Stripe (test|live)
|
||||||
|
* @returns {(function(Dispatch, GetState): Promise<undefined|void|*>)|*}
|
||||||
|
*/
|
||||||
export function doCommentCreate(
|
export function doCommentCreate(
|
||||||
comment: string = '',
|
comment: string = '',
|
||||||
claim_id: string = '',
|
claim_id: string = '',
|
||||||
parent_id?: string,
|
parent_id?: string,
|
||||||
uri: string,
|
uri: string,
|
||||||
livestream?: boolean = false,
|
livestream?: boolean = false,
|
||||||
txid?: string
|
txid?: string,
|
||||||
|
payment_intent_id?: string,
|
||||||
|
environment?: string,
|
||||||
) {
|
) {
|
||||||
return async (dispatch: Dispatch, getState: GetState) => {
|
return async (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
|
// get active channel that will receive comment and optional tip
|
||||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||||
|
|
||||||
if (!activeChannelClaim) {
|
if (!activeChannelClaim) {
|
||||||
|
@ -401,6 +417,7 @@ export function doCommentCreate(
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send a notification
|
||||||
if (parent_id) {
|
if (parent_id) {
|
||||||
const notification = makeSelectNotificationForCommentId(parent_id)(state);
|
const notification = makeSelectNotificationForCommentId(parent_id)(state);
|
||||||
if (notification && !notification.is_seen) {
|
if (notification && !notification.is_seen) {
|
||||||
|
@ -412,6 +429,8 @@ export function doCommentCreate(
|
||||||
return dispatch(doToast({ isError: true, message: __('Unable to verify your channel. Please try again.') }));
|
return dispatch(doToast({ isError: true, message: __('Unable to verify your channel. Please try again.') }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comments is a function which helps make calls to the backend
|
||||||
|
// these params passed in POST call.
|
||||||
return Comments.comment_create({
|
return Comments.comment_create({
|
||||||
comment: comment,
|
comment: comment,
|
||||||
claim_id: claim_id,
|
claim_id: claim_id,
|
||||||
|
@ -420,9 +439,12 @@ export function doCommentCreate(
|
||||||
parent_id: parent_id,
|
parent_id: parent_id,
|
||||||
signature: signatureData.signature,
|
signature: signatureData.signature,
|
||||||
signing_ts: signatureData.signing_ts,
|
signing_ts: signatureData.signing_ts,
|
||||||
...(txid ? { support_tx_id: txid } : {}),
|
...(txid ? { support_tx_id: txid } : {}), // add transaction id if it exists
|
||||||
|
...(payment_intent_id ? { payment_intent_id } : {}), // add payment_intent_id if it exists
|
||||||
|
...(environment ? { environment } : {}), // add environment for stripe if it exists
|
||||||
})
|
})
|
||||||
.then((result: CommentCreateResponse) => {
|
.then((result: CommentCreateResponse) => {
|
||||||
|
console.log(result);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_CREATE_COMPLETED,
|
type: ACTIONS.COMMENT_CREATE_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
|
@ -435,6 +457,7 @@ export function doCommentCreate(
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_CREATE_FAILED,
|
type: ACTIONS.COMMENT_CREATE_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
//plastic card image creator
|
||||||
|
.jp-card-name {
|
||||||
|
bottom: -7px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jp-card-container {
|
||||||
|
margin: 0 !important;
|
||||||
|
margin-top: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stripe__card-link-text {
|
||||||
|
color: rgb(253, 253, 253)
|
||||||
|
}
|
||||||
|
|
||||||
.date-header {
|
.date-header {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +29,7 @@
|
||||||
padding: 48px;
|
padding: 48px;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 400px;
|
height: 319px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue