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",
|
||||
"if-env": "^1.0.4",
|
||||
"react-datetime-picker": "^3.2.1",
|
||||
"react-plastic": "^1.1.1",
|
||||
"react-top-loading-bar": "^2.0.1",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"source-map-explorer": "^2.5.2",
|
||||
|
|
|
@ -2026,7 +2026,7 @@
|
|||
"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.",
|
||||
"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.",
|
||||
"Add card to tip creators in USD": "Add card to tip creators in USD",
|
||||
"Connect a bank account": "Connect a bank account",
|
||||
|
|
|
@ -59,6 +59,7 @@ type Props = {
|
|||
stakedLevel: number,
|
||||
supportAmount: number,
|
||||
numDirectReplies: number,
|
||||
isFiat: boolean
|
||||
};
|
||||
|
||||
const LENGTH_TO_COLLAPSE = 300;
|
||||
|
@ -91,6 +92,7 @@ function Comment(props: Props) {
|
|||
stakedLevel,
|
||||
supportAmount,
|
||||
numDirectReplies,
|
||||
isFiat,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
|
@ -240,7 +242,7 @@ function Comment(props: Props) {
|
|||
label={<DateTime date={timePosted} timeAgo />}
|
||||
/>
|
||||
|
||||
{supportAmount > 0 && <CreditAmount amount={supportAmount} superChatLight size={12} />}
|
||||
{supportAmount > 0 && <CreditAmount isFiat={isFiat} amount={supportAmount} superChatLight size={12} />}
|
||||
|
||||
{isPinned && (
|
||||
<span className="comment__pin">
|
||||
|
|
|
@ -12,6 +12,7 @@ import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
|||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { makeSelectCommentsDisabledForUri } from 'redux/selectors/comments';
|
||||
import { CommentCreate } from './view';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
|
||||
const select = (state, props) => ({
|
||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||
|
@ -24,11 +25,12 @@ const select = (state, props) => ({
|
|||
});
|
||||
|
||||
const perform = (dispatch, ownProps) => ({
|
||||
createComment: (comment, claimId, parentId, txid) =>
|
||||
dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, ownProps.livestream, txid)),
|
||||
createComment: (comment, claimId, parentId, txid, payment_intent_id, environment) =>
|
||||
dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, ownProps.livestream, txid, payment_intent_id, environment)),
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)),
|
||||
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
|
||||
doToast: (options) => dispatch(doToast(options)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(CommentCreate);
|
||||
|
|
|
@ -16,11 +16,23 @@ import CreditAmount from 'component/common/credit-amount';
|
|||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
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 = {
|
||||
uri: string,
|
||||
claim: StreamClaim,
|
||||
createComment: (string, string, string, ?string) => Promise<any>,
|
||||
createComment: (string, string, string, ?string, ?string, ?string) => Promise<any>,
|
||||
commentsDisabledBySettings: boolean,
|
||||
channels: ?Array<ChannelClaim>,
|
||||
onDoneReplying?: () => void,
|
||||
|
@ -35,6 +47,8 @@ type Props = {
|
|||
toast: (string) => void,
|
||||
claimIsMine: boolean,
|
||||
sendTip: ({}, (any) => void, (any) => void) => void,
|
||||
doToast: ({ message: string }) => void,
|
||||
disabled: boolean,
|
||||
};
|
||||
|
||||
export function CommentCreate(props: Props) {
|
||||
|
@ -53,8 +67,10 @@ export function CommentCreate(props: Props) {
|
|||
livestream,
|
||||
claimIsMine,
|
||||
sendTip,
|
||||
doToast,
|
||||
} = props;
|
||||
const buttonref: ElementRef<any> = React.useRef();
|
||||
|
||||
const {
|
||||
push,
|
||||
location: { pathname },
|
||||
|
@ -72,6 +88,14 @@ export function CommentCreate(props: Props) {
|
|||
const disabled = isSubmitting || !activeChannelClaim || !commentValue.length;
|
||||
const charCount = commentValue.length;
|
||||
|
||||
const [activeTab, setActiveTab] = React.useState('');
|
||||
|
||||
const [tipError, setTipError] = React.useState();
|
||||
|
||||
// React.useEffect(() => {
|
||||
// setTipError('yes');
|
||||
// }, []);
|
||||
|
||||
function handleCommentChange(event) {
|
||||
let commentValue;
|
||||
if (isReply) {
|
||||
|
@ -123,26 +147,109 @@ export function CommentCreate(props: Props) {
|
|||
channel_id: activeChannelClaim.claim_id,
|
||||
};
|
||||
|
||||
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||
const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
|
||||
|
||||
console.log(activeChannelClaim);
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
if (activeTab === TAB_LBC) {
|
||||
// call sendTip and then run the callback from the response
|
||||
// second parameter is callback
|
||||
sendTip(
|
||||
params,
|
||||
(response) => {
|
||||
const { txid } = response;
|
||||
// todo: why the setTimeout?
|
||||
setTimeout(() => {
|
||||
handleCreateComment(txid);
|
||||
}, 1500);
|
||||
setSuccessTip({ txid, tipAmount });
|
||||
},
|
||||
() => {
|
||||
// reset the frontend so people can send a new comment
|
||||
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);
|
||||
createComment(commentValue, claimId, parentId, txid)
|
||||
|
||||
createComment(commentValue, claimId, parentId, txid, payment_intent_id, environment)
|
||||
.then((res) => {
|
||||
setIsSubmitting(false);
|
||||
|
||||
|
@ -157,7 +264,7 @@ export function CommentCreate(props: Props) {
|
|||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((e) => {
|
||||
setIsSubmitting(false);
|
||||
setCommentFailure(true);
|
||||
});
|
||||
|
@ -198,10 +305,11 @@ export function CommentCreate(props: Props) {
|
|||
}
|
||||
|
||||
if (isReviewingSupportComment && activeChannelClaim) {
|
||||
|
||||
return (
|
||||
<div className="comment__create">
|
||||
<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} />
|
||||
<div>
|
||||
|
@ -262,15 +370,17 @@ export function CommentCreate(props: Props) {
|
|||
autoFocus={isReply}
|
||||
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">
|
||||
{isSupportComment ? (
|
||||
<>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
// TODO: add better check here
|
||||
disabled={disabled || tipError}
|
||||
type="button"
|
||||
button="primary"
|
||||
icon={ICONS.LBC}
|
||||
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
|
||||
label={__('Review')}
|
||||
onClick={() => setIsReviewingSupportComment(true)}
|
||||
/>
|
||||
|
@ -296,7 +406,16 @@ export function CommentCreate(props: Props) {
|
|||
requiresAuth={IS_WEB}
|
||||
/>
|
||||
{!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 && (
|
||||
<Button
|
||||
|
|
|
@ -307,6 +307,7 @@ function CommentList(props: Props) {
|
|||
isPinned={comment.is_pinned}
|
||||
supportAmount={comment.support_amount}
|
||||
numDirectReplies={comment.replies}
|
||||
isFiat={comment.is_fiat}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -18,6 +18,7 @@ type Props = {
|
|||
size?: number,
|
||||
superChat?: boolean,
|
||||
superChatLight?: boolean,
|
||||
isFiat?: boolean
|
||||
};
|
||||
|
||||
class CreditAmount extends React.PureComponent<Props> {
|
||||
|
@ -45,6 +46,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
size,
|
||||
superChat,
|
||||
superChatLight,
|
||||
isFiat,
|
||||
} = this.props;
|
||||
const minimumRenderableAmount = 10 ** (-1 * precision);
|
||||
const fullPrice = formatFullPrice(amount, 2);
|
||||
|
@ -70,8 +72,10 @@ class CreditAmount extends React.PureComponent<Props> {
|
|||
amountText = `+${amountText}`;
|
||||
}
|
||||
|
||||
if (showLBC) {
|
||||
if (showLBC && !isFiat) {
|
||||
amountText = <LbcSymbol postfix={amountText} size={size} />;
|
||||
} else if (showLBC && isFiat) {
|
||||
amountText = <p> ${(Math.round(amountText * 100) / 100).toFixed(2)}</p>;
|
||||
}
|
||||
|
||||
if (fee) {
|
||||
|
|
|
@ -20,10 +20,11 @@ type Props = {
|
|||
commentIsMine: boolean,
|
||||
stakedLevel: number,
|
||||
supportAmount: number,
|
||||
isFiat: boolean,
|
||||
};
|
||||
|
||||
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 commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
|
||||
const { claimName } = parseURI(authorUri);
|
||||
|
@ -39,7 +40,7 @@ function LivestreamComment(props: Props) {
|
|||
{supportAmount > 0 && (
|
||||
<div className="super-chat livestream-superchat__banner">
|
||||
<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>
|
||||
)}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export default function LivestreamComments(props: Props) {
|
|||
superChatsTotalAmount,
|
||||
myChannels,
|
||||
} = props;
|
||||
|
||||
const commentsRef = React.createRef();
|
||||
const [scrollBottom, setScrollBottom] = React.useState(true);
|
||||
const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT);
|
||||
|
@ -174,6 +175,7 @@ export default function LivestreamComments(props: Props) {
|
|||
size={10}
|
||||
className="livestream-superchat__amount-large"
|
||||
amount={superChat.support_amount}
|
||||
isFiat={superChat.is_fiat}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -193,6 +195,7 @@ export default function LivestreamComments(props: Props) {
|
|||
commentId={comment.comment_id}
|
||||
message={comment.comment}
|
||||
supportAmount={comment.support_amount}
|
||||
isFiat={comment.is_fiat}
|
||||
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 SettingsAdvancedPage = lazyImport(() => import('page/settingsAdvanced' /* 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 SettingsNotificationsPage = lazyImport(() =>
|
||||
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_STRIPE_CARD}`} component={SettingsStripeCard} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_STRIPE_ACCOUNT}`} component={SettingsStripeAccount} />
|
||||
<PrivateRoute
|
||||
{...props}
|
||||
exact
|
||||
|
|
|
@ -96,10 +96,7 @@ function WalletSendTip(props: Props) {
|
|||
|
||||
const sourceClaimId = claim.claim_id;
|
||||
|
||||
// TODO: come up with a better way to do this,
|
||||
// TODO: waiting 100ms to wait for token to populate
|
||||
|
||||
// check if creator has an account saved
|
||||
// check if creator has a payment method saved
|
||||
React.useEffect(() => {
|
||||
if (channelClaimId && isAuthenticated) {
|
||||
Lbryio.call(
|
||||
|
@ -121,6 +118,12 @@ function WalletSendTip(props: Props) {
|
|||
}
|
||||
}, [channelClaimId, isAuthenticated]);
|
||||
|
||||
// check if creator has an account saved
|
||||
React.useEffect(() => {
|
||||
var tipInputElement = document.getElementById('tip-input');
|
||||
if (tipInputElement) { tipInputElement.focus() }
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (channelClaimId) {
|
||||
Lbryio.call(
|
||||
|
@ -139,7 +142,7 @@ function WalletSendTip(props: Props) {
|
|||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
// console.log(error);
|
||||
});
|
||||
}
|
||||
}, [channelClaimId]);
|
||||
|
@ -170,7 +173,7 @@ function WalletSendTip(props: Props) {
|
|||
explainerText = __('This refundable boost will improve the discoverability of this %claimTypeText% while active.', {claimTypeText});
|
||||
} else if (activeTab === TAB_FIAT) {
|
||||
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) {
|
||||
// explainerText += __('You must add a card to use this functionality.');
|
||||
// }
|
||||
|
@ -187,22 +190,34 @@ function WalletSendTip(props: Props) {
|
|||
const validTipInput = regexp.test(String(tipAmount));
|
||||
let tipError;
|
||||
|
||||
if (!tipAmount) {
|
||||
tipError = __('Amount must be a number');
|
||||
} else if (tipAmount <= 0) {
|
||||
if (tipAmount === 0) {
|
||||
tipError = __('Amount must be a positive number');
|
||||
} else if (tipAmount < MINIMUM_PUBLISH_BID) {
|
||||
tipError = __('Amount must be higher');
|
||||
} else if (!validTipInput) {
|
||||
} else if (!tipAmount || typeof tipAmount !== 'number') {
|
||||
tipError = __('Amount must be a number');
|
||||
}
|
||||
|
||||
// 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');
|
||||
} else if (tipAmount === balance) {
|
||||
tipError = __('Please decrease the amount to account for transaction fees');
|
||||
} else if (tipAmount > balance) {
|
||||
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);
|
||||
}, [tipAmount, balance, setTipError]);
|
||||
}, [tipAmount, balance, setTipError, activeTab]);
|
||||
|
||||
//
|
||||
function sendSupportOrConfirm(instantTipMaxAmount = null) {
|
||||
|
@ -267,11 +282,15 @@ function WalletSendTip(props: Props) {
|
|||
tipChannelName,
|
||||
}),
|
||||
});
|
||||
console.log(customerTipResponse);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
doToast({ message: error.message, isError: true });
|
||||
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 });
|
||||
});
|
||||
|
||||
closeModal();
|
||||
|
@ -285,6 +304,7 @@ function WalletSendTip(props: Props) {
|
|||
|
||||
function handleCustomPriceChange(event: SyntheticInputEvent<*>) {
|
||||
const tipAmount = parseFloat(event.target.value);
|
||||
|
||||
setCustomTipAmount(tipAmount);
|
||||
}
|
||||
|
||||
|
@ -368,6 +388,8 @@ function WalletSendTip(props: Props) {
|
|||
label={__('Tip')}
|
||||
button="alt"
|
||||
onClick={() => {
|
||||
var tipInputElement = document.getElementById('tip-input');
|
||||
if (tipInputElement) { tipInputElement.focus() }
|
||||
if (!isConfirming) {
|
||||
setActiveTab(TAB_LBC);
|
||||
}
|
||||
|
@ -382,6 +404,8 @@ function WalletSendTip(props: Props) {
|
|||
label={__('Tip')}
|
||||
button="alt"
|
||||
onClick={() => {
|
||||
var tipInputElement = document.getElementById('tip-input');
|
||||
if (tipInputElement) { tipInputElement.focus() }
|
||||
if (!isConfirming) {
|
||||
setActiveTab(TAB_FIAT);
|
||||
}
|
||||
|
@ -396,6 +420,8 @@ function WalletSendTip(props: Props) {
|
|||
label={__('Boost')}
|
||||
button="alt"
|
||||
onClick={() => {
|
||||
var tipInputElement = document.getElementById('tip-input');
|
||||
if (tipInputElement) { tipInputElement.focus() }
|
||||
if (!isConfirming) {
|
||||
setActiveTab(TAB_BOOST);
|
||||
}
|
||||
|
@ -483,6 +509,7 @@ function WalletSendTip(props: Props) {
|
|||
icon={iconToUse}
|
||||
label={__('Custom')}
|
||||
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)}
|
||||
/>
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectBalance } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, selectBalance } from 'lbry-redux';
|
||||
import WalletTipAmountSelector from './view';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
const select = (state, props) => ({
|
||||
balance: selectBalance(state),
|
||||
isAuthenticated: Boolean(selectUserVerifiedEmail(state)),
|
||||
// claim: makeSelectClaimForUri(props.uri)(state),
|
||||
// claim: makeSelectClaimForUri(props.uri, false)(state),
|
||||
});
|
||||
|
||||
export default connect(select)(WalletTipAmountSelector);
|
||||
|
|
|
@ -10,40 +10,145 @@ import I18nMessage from 'component/i18nMessage';
|
|||
import classnames from 'classnames';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
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 TAB_FIAT = 'TabFiat';
|
||||
const TAB_LBC = 'TabLBC';
|
||||
|
||||
type Props = {
|
||||
balance: number,
|
||||
amount: number,
|
||||
onChange: (number) => void,
|
||||
isAuthenticated: boolean,
|
||||
claim: StreamClaim,
|
||||
uri: string,
|
||||
onTipErrorChange: (string) => void,
|
||||
};
|
||||
|
||||
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 [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(() => {
|
||||
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 validTipInput = regexp.test(String(amount));
|
||||
let tipError;
|
||||
|
||||
if (!amount) {
|
||||
tipError = __('Amount must be a number');
|
||||
} else if (amount <= 0) {
|
||||
if (amount === 0) {
|
||||
tipError = __('Amount must be a positive number');
|
||||
} else if (amount < MINIMUM_PUBLISH_BID) {
|
||||
tipError = __('Amount must be higher');
|
||||
} else if (!validTipInput) {
|
||||
} else if (!amount || typeof amount !== 'number') {
|
||||
tipError = __('Amount must be a number');
|
||||
}
|
||||
|
||||
// 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');
|
||||
} else if (amount === balance) {
|
||||
tipError = __('Please decrease the amount to account for transaction fees');
|
||||
} else if (amount > balance) {
|
||||
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);
|
||||
}, [amount, balance, setTipError]);
|
||||
onTipErrorChange(tipError);
|
||||
}, [amount, balance, setTipError, activeTab]);
|
||||
|
||||
function handleCustomPriceChange(amount: number) {
|
||||
const tipAmount = parseFloat(amount);
|
||||
|
@ -56,14 +161,14 @@ function WalletTipAmountSelector(props: Props) {
|
|||
{DEFAULT_TIP_AMOUNTS.map((defaultAmount) => (
|
||||
<Button
|
||||
key={defaultAmount}
|
||||
disabled={amount > balance}
|
||||
disabled={shouldDisableAmountSelector(defaultAmount)}
|
||||
button="alt"
|
||||
className={classnames('button-toggle button-toggle--expandformobile', {
|
||||
'button-toggle--active': defaultAmount === amount,
|
||||
'button-toggle--active': defaultAmount === amount && !useCustomTip,
|
||||
'button-toggle--disabled': amount > balance,
|
||||
})}
|
||||
label={defaultAmount}
|
||||
icon={ICONS.LBC}
|
||||
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
|
||||
onClick={() => {
|
||||
handleCustomPriceChange(defaultAmount);
|
||||
setUseCustomTip(false);
|
||||
|
@ -72,14 +177,15 @@ function WalletTipAmountSelector(props: Props) {
|
|||
))}
|
||||
<Button
|
||||
button="alt"
|
||||
disabled={activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip)}
|
||||
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')}
|
||||
onClick={() => setUseCustomTip(true)}
|
||||
/>
|
||||
{DEFAULT_TIP_AMOUNTS.some((val) => val > balance) && (
|
||||
{activeTab === TAB_LBC && DEFAULT_TIP_AMOUNTS.some((val) => val > balance) && (
|
||||
<Button
|
||||
button="secondary"
|
||||
className="button-toggle-group-action"
|
||||
|
@ -90,18 +196,55 @@ function WalletTipAmountSelector(props: Props) {
|
|||
)}
|
||||
</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 && (
|
||||
<div className="comment__tip-input">
|
||||
<FormField
|
||||
autoFocus
|
||||
name="tip-input"
|
||||
label={
|
||||
label={ activeTab === TAB_LBC ?
|
||||
<React.Fragment>
|
||||
{__('Custom support amount')}{' '}
|
||||
<I18nMessage tokens={{ lbc_balance: <CreditAmount precision={4} amount={balance} /> }}>
|
||||
(%lbc_balance% available)
|
||||
</I18nMessage>
|
||||
</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"
|
||||
error={tipError}
|
||||
|
@ -115,7 +258,38 @@ function WalletTipAmountSelector(props: Props) {
|
|||
</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 COLLECTION_ADD = 'collection_add';
|
||||
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.SETTINGS = 'settings';
|
||||
exports.SETTINGS_STRIPE_CARD = 'settings/card';
|
||||
exports.SETTINGS_STRIPE_ACCOUNT = 'settings/tip_account';
|
||||
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
||||
exports.SETTINGS_ADVANCED = 'settings/advanced';
|
||||
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 ModalPublishPreview = lazyImport(() => import('modal/modalPublishPreview' /* webpackChunkName: "modalPublishPreview" */));
|
||||
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 ModalRevokeClaim = lazyImport(() => import('modal/modalRevokeClaim' /* webpackChunkName: "modalRevokeClaim" */));
|
||||
const ModalRewardCode = lazyImport(() => import('modal/modalRewardCode' /* webpackChunkName: "modalRewardCode" */));
|
||||
|
@ -151,6 +152,8 @@ function ModalRouter(props: Props) {
|
|||
return ModalClaimCollectionAdd;
|
||||
case MODALS.COLLECTION_DELETE:
|
||||
return ModalDeleteCollection;
|
||||
case MODALS.CONFIRM_REMOVE_CARD:
|
||||
return ModalRemoveCard;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from 'redux/selectors/settings';
|
||||
import { doWalletStatus, selectMyChannelUrls, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
|
||||
import SettingsPage from './view';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||
|
||||
const select = (state) => ({
|
||||
daemonSettings: selectDaemonSettings(state),
|
||||
|
@ -38,6 +38,7 @@ const select = (state) => ({
|
|||
darkModeTimes: makeSelectClientSetting(SETTINGS.DARK_MODE_TIMES)(state),
|
||||
language: selectLanguage(state),
|
||||
myChannelUrls: selectMyChannelUrls(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -72,6 +72,7 @@ type Props = {
|
|||
enterSettings: () => void,
|
||||
exitSettings: () => void,
|
||||
myChannelUrls: ?Array<string>,
|
||||
user: User,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -189,6 +190,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
clearCache,
|
||||
openModal,
|
||||
myChannelUrls,
|
||||
user,
|
||||
} = this.props;
|
||||
const { storedPassword } = this.state;
|
||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||
|
@ -206,14 +208,32 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
className="card-stack"
|
||||
>
|
||||
{/* @if TARGET='web' */}
|
||||
<Card
|
||||
title={__('Add card to tip creators in USD')}
|
||||
{user && user.fiat_enabled && <Card
|
||||
title={__('Bank Accounts')}
|
||||
subtitle={__('Connect a bank account to receive tips and compensation in your local currency')}
|
||||
actions={
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="secondary"
|
||||
label={__('Manage Card')}
|
||||
icon={ICONS.WALLET}
|
||||
label={__('Manage')}
|
||||
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}`}
|
||||
/>
|
||||
</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 { selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||
import { selectUserVerifiedEmail, selectUserEmail } from 'redux/selectors/user';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
|
||||
import SettingsStripeCard from './view';
|
||||
|
||||
|
@ -13,6 +15,9 @@ const select = (state) => ({
|
|||
|
||||
const perform = (dispatch) => ({
|
||||
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);
|
||||
|
|
|
@ -7,10 +7,10 @@ import Card from 'component/common/card';
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import { STRIPE_PUBLIC_KEY } from 'config';
|
||||
import moment from 'moment';
|
||||
|
||||
let scriptLoading = false;
|
||||
// let scriptLoaded = false;
|
||||
// let scriptDidError = false; // these could probably be in state if managing locally
|
||||
import Plastic from 'react-plastic';
|
||||
import Button from 'component/button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
|
||||
let stripeEnvironment = 'test';
|
||||
// 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';
|
||||
}
|
||||
|
||||
// type Props = {
|
||||
// disabled: boolean,
|
||||
// label: ?string,
|
||||
// email: ?string,
|
||||
// scriptFailedToLoad: boolean,
|
||||
// };
|
||||
// eslint-disable-next-line flowtype/no-types-missing-file-annotation
|
||||
type Props = {
|
||||
disabled: boolean,
|
||||
label: ?string,
|
||||
email: ?string,
|
||||
scriptFailedToLoad: boolean,
|
||||
doOpenModal: (string, {}) => void,
|
||||
openModal: (string, {}) => void,
|
||||
setAsConfirmingCard: () => void,
|
||||
};
|
||||
//
|
||||
// type State = {
|
||||
// open: boolean,
|
||||
|
@ -46,12 +50,17 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
customerTransactions: [],
|
||||
pageTitle: 'Add Card',
|
||||
userCardDetails: {},
|
||||
paymentMethodId: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
var that = this;
|
||||
|
||||
console.log(this.props);
|
||||
|
||||
var doToast = this.props.doToast;
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://js.stripe.com/v3/';
|
||||
script.async = true;
|
||||
|
@ -86,33 +95,27 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
if (userHasAlreadySetupPayment) {
|
||||
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 = {
|
||||
brand: card.brand,
|
||||
expiryYear: card.exp_year,
|
||||
expiryMonth: card.exp_month,
|
||||
lastFour: card.last4,
|
||||
topOfDisplay: topOfDisplay,
|
||||
bottomOfDisplay: bottomOfDisplay,
|
||||
};
|
||||
|
||||
that.setState({
|
||||
currentFlowStage: 'cardConfirmed',
|
||||
pageTitle: 'Tip History',
|
||||
userCardDetails: cardDetails,
|
||||
});
|
||||
|
||||
// get customer transactions
|
||||
Lbryio.call(
|
||||
'customer',
|
||||
'list',
|
||||
{
|
||||
environment: stripeEnvironment,
|
||||
},
|
||||
'post'
|
||||
).then((customerTransactionsResponse) => {
|
||||
that.setState({
|
||||
customerTransactions: customerTransactionsResponse,
|
||||
});
|
||||
|
||||
console.log(customerTransactionsResponse);
|
||||
paymentMethodId: customerStatusResponse.PaymentMethods[0].id,
|
||||
});
|
||||
|
||||
// otherwise, prompt them to save a card
|
||||
|
@ -138,6 +141,22 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
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
|
||||
})
|
||||
.catch(function(error) {
|
||||
|
@ -147,7 +166,7 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
const errorString = 'user as customer is not setup 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
|
||||
that.setState({
|
||||
currentFlowStage: 'confirmingCard',
|
||||
|
@ -169,12 +188,16 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
// instantiate stripe elements
|
||||
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 {
|
||||
console.log('Unseen before error');
|
||||
}
|
||||
});
|
||||
}, 250);
|
||||
|
||||
|
||||
function setupStripe() {
|
||||
// TODO: have to fix this, using so that the script is available
|
||||
setTimeout(function() {
|
||||
|
@ -302,17 +325,25 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
).then((customerStatusResponse) => {
|
||||
var card = customerStatusResponse.PaymentMethods[0].card;
|
||||
|
||||
var customer = customerStatusResponse.Customer;
|
||||
|
||||
var topOfDisplay = customer.email.split('@')[0];
|
||||
var bottomOfDisplay = '@' + customer.email.split('@')[1];
|
||||
|
||||
var cardDetails = {
|
||||
brand: card.brand,
|
||||
expiryYear: card.exp_year,
|
||||
expiryMonth: card.exp_month,
|
||||
lastFour: card.last4,
|
||||
topOfDisplay,
|
||||
bottomOfDisplay,
|
||||
};
|
||||
|
||||
that.setState({
|
||||
currentFlowStage: 'cardConfirmed',
|
||||
pageTitle: 'Tip History',
|
||||
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() {
|
||||
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 (
|
||||
<Page backout={{ title: pageTitle, backLabel: __('Done') }} noFooter noSideNavigation>
|
||||
|
@ -393,6 +384,7 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* customer has not added a card yet */}
|
||||
{currentFlowStage === 'confirmingCard' && (
|
||||
<div className="sr-root">
|
||||
<div className="sr-main">
|
||||
|
@ -411,21 +403,39 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* if the user has already confirmed their card */}
|
||||
{currentFlowStage === 'cardConfirmed' && (
|
||||
<div className="successCard">
|
||||
<Card
|
||||
title={__('Card Details')}
|
||||
body={
|
||||
<>
|
||||
<h4 className="grey-text">
|
||||
Brand: {userCardDetails.brand.toUpperCase()} Last 4: {userCardDetails.lastFour}
|
||||
Expires: {userCardDetails.expiryMonth}/{userCardDetails.expiryYear}
|
||||
</h4>
|
||||
<Plastic
|
||||
type={userCardDetails.brand}
|
||||
name={userCardDetails.topOfDisplay + ' ' + userCardDetails.bottomOfDisplay}
|
||||
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 />
|
||||
|
||||
{/* if a user has no transactions yet */}
|
||||
{(!customerTransactions || customerTransactions.length === 0) && (
|
||||
<Card
|
||||
title={__('Tip History')}
|
||||
|
@ -433,6 +443,10 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* customer already has transactions */}
|
||||
{customerTransactions && customerTransactions.length > 0 && (
|
||||
<Card
|
||||
title={__('Tip History')}
|
||||
|
@ -444,16 +458,33 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
<tr>
|
||||
<th className="date-header">{__('Date')}</th>
|
||||
<th>{<>{__('Receiving Channel Name')}</>}</th>
|
||||
<th>{__('Tip Location')}</th>
|
||||
<th>{__('Amount (USD)')} </th>
|
||||
<th>{__('Anonymous')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{customerTransactions &&
|
||||
customerTransactions.map((transaction) => (
|
||||
customerTransactions.reverse().map((transaction) => (
|
||||
<tr key={transaction.name + transaction.created_at}>
|
||||
<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.private_tip ? 'Yes' : 'No'}</td>
|
||||
</tr>
|
||||
|
@ -465,8 +496,7 @@ class SettingsStripeCard extends React.Component<Props, State> {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,9 +34,6 @@ const WalletPage = (props: Props) => {
|
|||
) : (
|
||||
<div className="card-stack">
|
||||
<WalletBalance />
|
||||
{/* @if TARGET='web' */}
|
||||
<StripeAccountConnection />
|
||||
{/* @endif */}
|
||||
<TxoList search={search} />
|
||||
</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(
|
||||
comment: string = '',
|
||||
claim_id: string = '',
|
||||
parent_id?: string,
|
||||
uri: string,
|
||||
livestream?: boolean = false,
|
||||
txid?: string
|
||||
txid?: string,
|
||||
payment_intent_id?: string,
|
||||
environment?: string,
|
||||
) {
|
||||
return async (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
|
||||
// get active channel that will receive comment and optional tip
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
|
||||
if (!activeChannelClaim) {
|
||||
|
@ -401,6 +417,7 @@ export function doCommentCreate(
|
|||
} catch (e) {}
|
||||
}
|
||||
|
||||
// send a notification
|
||||
if (parent_id) {
|
||||
const notification = makeSelectNotificationForCommentId(parent_id)(state);
|
||||
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.') }));
|
||||
}
|
||||
|
||||
// Comments is a function which helps make calls to the backend
|
||||
// these params passed in POST call.
|
||||
return Comments.comment_create({
|
||||
comment: comment,
|
||||
claim_id: claim_id,
|
||||
|
@ -420,9 +439,12 @@ export function doCommentCreate(
|
|||
parent_id: parent_id,
|
||||
signature: signatureData.signature,
|
||||
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) => {
|
||||
console.log(result);
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_CREATE_COMPLETED,
|
||||
data: {
|
||||
|
@ -435,6 +457,7 @@ export function doCommentCreate(
|
|||
return result;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_CREATE_FAILED,
|
||||
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 {
|
||||
width: 30%;
|
||||
}
|
||||
|
@ -15,7 +29,7 @@
|
|||
padding: 48px;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
height: 400px;
|
||||
height: 319px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue