lbry-desktop/ui/component/commentCreate/view.jsx
Anthony 7bb5df97fd 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
2021-07-17 13:19:33 -04:00

436 lines
14 KiB
JavaScript

// @flow
import type { ElementRef } from 'react';
import { SIMPLE_SITE } from 'config';
import * as PAGES from 'constants/pages';
import * as ICONS from 'constants/icons';
import React from 'react';
import classnames from 'classnames';
import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import SelectChannel from 'component/selectChannel';
import usePersistedState from 'effects/use-persisted-state';
import { FF_MAX_CHARS_IN_COMMENT, FF_MAX_CHARS_IN_LIVESTREAM_COMMENT } from 'constants/form-field';
import { useHistory } from 'react-router';
import WalletTipAmountSelector from 'component/walletTipAmountSelector';
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, ?string, ?string) => Promise<any>,
commentsDisabledBySettings: boolean,
channels: ?Array<ChannelClaim>,
onDoneReplying?: () => void,
onCancelReplying?: () => void,
isNested: boolean,
isFetchingChannels: boolean,
parentId: string,
isReply: boolean,
activeChannel: string,
activeChannelClaim: ?ChannelClaim,
livestream?: boolean,
toast: (string) => void,
claimIsMine: boolean,
sendTip: ({}, (any) => void, (any) => void) => void,
doToast: ({ message: string }) => void,
disabled: boolean,
};
export function CommentCreate(props: Props) {
const {
createComment,
commentsDisabledBySettings,
claim,
channels,
onDoneReplying,
onCancelReplying,
isNested,
isFetchingChannels,
isReply,
parentId,
activeChannelClaim,
livestream,
claimIsMine,
sendTip,
doToast,
} = props;
const buttonref: ElementRef<any> = React.useRef();
const {
push,
location: { pathname },
} = useHistory();
const [isSubmitting, setIsSubmitting] = React.useState(false);
const [commentFailure, setCommentFailure] = React.useState(false);
const [successTip, setSuccessTip] = React.useState({ txid: undefined, tipAmount: undefined });
const { claim_id: claimId } = claim;
const [isSupportComment, setIsSupportComment] = React.useState();
const [isReviewingSupportComment, setIsReviewingSupportComment] = React.useState();
const [tipAmount, setTipAmount] = React.useState(1);
const [commentValue, setCommentValue] = React.useState('');
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
const hasChannels = channels && channels.length;
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) {
commentValue = event.target.value;
} else {
commentValue = !SIMPLE_SITE && advancedEditor ? event : event.target.value;
}
setCommentValue(commentValue);
}
function altEnterListener(e: SyntheticKeyboardEvent<*>) {
const KEYCODE_ENTER = 13;
if ((e.ctrlKey || e.metaKey) && e.keyCode === KEYCODE_ENTER) {
e.preventDefault();
buttonref.current.click();
}
}
function onTextareaFocus() {
window.addEventListener('keydown', altEnterListener);
}
function onTextareaBlur() {
window.removeEventListener('keydown', altEnterListener);
}
function handleSubmit() {
if (activeChannelClaim && commentValue.length) {
handleCreateComment();
}
}
function handleSupportComment() {
if (!activeChannelClaim) {
return;
}
if (commentFailure && tipAmount === successTip.tipAmount) {
handleCreateComment(successTip.txid);
return;
} else {
setSuccessTip({ txid: undefined, tipAmount: undefined });
}
const params = {
amount: tipAmount,
claim_id: claimId,
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;
}
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, payment_intent_id, environment)
.then((res) => {
setIsSubmitting(false);
if (res && res.signature) {
setCommentValue('');
setIsReviewingSupportComment(false);
setIsSupportComment(false);
setCommentFailure(false);
if (onDoneReplying) {
onDoneReplying();
}
}
})
.catch((e) => {
setIsSubmitting(false);
setCommentFailure(true);
});
}
function toggleEditorMode() {
setAdvancedEditor(!advancedEditor);
}
if (commentsDisabledBySettings) {
return <Empty padded text={__('This channel has disabled comments on their page.')} />;
}
if (!hasChannels) {
return (
<div
role="button"
onClick={() => {
const pathPlusRedirect = `/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`;
if (livestream) {
window.open(pathPlusRedirect);
} else {
push(pathPlusRedirect);
}
}}
>
<FormField
type="textarea"
name={'comment_signup_prompt'}
placeholder={__('Say something about this...')}
label={isFetchingChannels ? __('Comment') : undefined}
/>
<div className="section__actions--no-margin">
<Button disabled button="primary" label={__('Post --[button to submit something]--')} requiresAuth={IS_WEB} />
</div>
</div>
);
}
if (isReviewingSupportComment && activeChannelClaim) {
return (
<div className="comment__create">
<div className="comment__sc-preview">
<CreditAmount className="comment__scpreview-amount" isFiat={activeTab === TAB_FIAT} amount={tipAmount} size={activeTab === TAB_LBC ? 18 : 2} />
<ChannelThumbnail xsmall uri={activeChannelClaim.canonical_url} />
<div>
<UriIndicator uri={activeChannelClaim.name} link />
<div>{commentValue}</div>
</div>
</div>
<div className="section__actions--no-margin">
<Button
autoFocus
button="primary"
disabled={disabled}
label={
isSubmitting
? __('Sending...')
: commentFailure && tipAmount === successTip.tipAmount
? __('Re-submit')
: __('Send')
}
onClick={handleSupportComment}
/>
<Button button="link" label={__('Cancel')} onClick={() => setIsReviewingSupportComment(false)} />
</div>
</div>
);
}
return (
<Form
onSubmit={handleSubmit}
className={classnames('comment__create', {
'comment__create--reply': isReply,
'comment__create--nested-reply': isNested,
})}
>
<FormField
disabled={!activeChannelClaim}
type={SIMPLE_SITE ? 'textarea' : advancedEditor && !isReply ? 'markdown' : 'textarea'}
name={isReply ? 'content_reply' : 'content_description'}
label={
<span className="comment-new__label-wrapper">
{!livestream && (
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
)}
<SelectChannel tiny />
</span>
}
quickActionLabel={
!SIMPLE_SITE && (isReply ? undefined : advancedEditor ? __('Simple Editor') : __('Advanced Editor'))
}
quickActionHandler={!SIMPLE_SITE && toggleEditorMode}
onFocus={onTextareaFocus}
onBlur={onTextareaBlur}
placeholder={__('Say something about this...')}
value={commentValue}
charCount={charCount}
onChange={handleCommentChange}
autoFocus={isReply}
textAreaMaxLength={livestream ? FF_MAX_CHARS_IN_LIVESTREAM_COMMENT : FF_MAX_CHARS_IN_COMMENT}
/>
{/* 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
// TODO: add better check here
disabled={disabled || tipError}
type="button"
button="primary"
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
label={__('Review')}
onClick={() => setIsReviewingSupportComment(true)}
/>
<Button disabled={disabled} button="link" label={__('Cancel')} onClick={() => setIsSupportComment(false)} />
</>
) : (
<>
<Button
ref={buttonref}
button="primary"
disabled={disabled}
type="submit"
label={
isReply
? isSubmitting
? __('Replying...')
: __('Reply')
: isSubmitting
? __('Commenting...')
: __('Comment --[button to submit something]--')
}
requiresAuth={IS_WEB}
/>
{!claimIsMine && (
<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
button="link"
label={__('Cancel')}
onClick={() => {
if (onCancelReplying) {
onCancelReplying();
}
}}
/>
)}
</>
)}
</div>
</Form>
);
}