// @flow import { Form } from 'component/common/form'; import LbcMessage from 'component/common/lbc-message'; import { Lbryio } from 'lbryinc'; import { parseURI } from 'util/lbryURI'; import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; import Button from 'component/button'; import Card from 'component/common/card'; import ChannelSelector from 'component/channelSelector'; import classnames from 'classnames'; import I18nMessage from 'component/i18nMessage'; import LbcSymbol from 'component/common/lbc-symbol'; import React from 'react'; import usePersistedState from 'effects/use-persisted-state'; import WalletTipAmountSelector from 'component/walletTipAmountSelector'; import { getStripeEnvironment } from 'util/stripe'; const stripeEnvironment = getStripeEnvironment(); const TAB_BOOST = 'TabBoost'; const TAB_FIAT = 'TabFiat'; const TAB_LBC = 'TabLBC'; type SupportParams = { amount: number, claim_id: string, channel_id?: string }; type TipParams = { tipAmount: number, tipChannelName: string, channelClaimId: string }; type UserParams = { activeChannelName: ?string, activeChannelId: ?string }; type Props = { activeChannelId?: string, activeChannelName?: string, balance: number, claimId?: string, claimType?: string, channelClaimId?: string, tipChannelName?: string, claimIsMine: boolean, fetchingChannels: boolean, incognito: boolean, instantTipEnabled: boolean, instantTipMax: { amount: number, currency: string }, isPending: boolean, isSupport: boolean, title: string, uri: string, isTipOnly?: boolean, hasSelectedTab?: string, customText?: string, doHideModal: () => void, doSendCashTip: ( TipParams, anonymous: boolean, UserParams, claimId: string, stripe: ?string, preferredCurrency: string, ?(any) => void ) => string, doSendTip: (SupportParams, boolean) => void, // function that comes from lbry-redux setAmount?: (number) => void, preferredCurrency: string, }; export default function WalletSendTip(props: Props) { const { activeChannelId, activeChannelName, balance, claimId, claimType, channelClaimId, tipChannelName, claimIsMine, fetchingChannels, incognito, instantTipEnabled, instantTipMax, isPending, title, uri, isTipOnly, hasSelectedTab, customText, doHideModal, doSendCashTip, doSendTip, setAmount, preferredCurrency, } = props; /** WHAT TAB TO SHOW **/ // if it's your content, we show boost, otherwise default is LBC const defaultTabToShow = claimIsMine ? TAB_BOOST : TAB_FIAT; // loads the default tab if nothing else is there yet const [persistentTab, setPersistentTab] = usePersistedState('send-tip-modal', defaultTabToShow); const [activeTab, setActiveTab] = React.useState(persistentTab); const [hasSelected, setSelected] = React.useState(false); /** STATE **/ const [tipAmount, setTipAmount] = usePersistedState('comment-support:customTip', 1.0); const [isOnConfirmationPage, setConfirmationPage] = React.useState(false); const [tipError, setTipError] = React.useState(); const [disableSubmitButton, setDisableSubmitButton] = React.useState(); /** CONSTS **/ const claimTypeText = getClaimTypeText(); const isSupport = claimIsMine || activeTab === TAB_BOOST; // text for modal header const titleText = isSupport ? __(claimIsMine ? 'Boost Your %claimTypeText%' : 'Boost This %claimTypeText%', { claimTypeText }) : __('Tip This %claimTypeText%', { claimTypeText }); let channelName; try { ({ channelName } = parseURI(uri)); } catch (e) {} // icon to use or explainer text to show per tab let explainerText = ''; switch (activeTab) { case TAB_BOOST: explainerText = __( 'This refundable boost will improve the discoverability of this %claimTypeText% while active.', { claimTypeText } ); break; case TAB_FIAT: case TAB_LBC: explainerText = __('Show this channel your appreciation by sending a donation.'); break; } /** FUNCTIONS **/ function getClaimTypeText() { switch (claimType) { case 'stream': return __('Content'); case 'channel': return __('Channel'); case 'repost': return __('Repost'); case 'collection': return __('List'); default: return __('Claim'); } } // make call to the backend to send lbc or fiat function sendSupportOrConfirm(instantTipMaxAmount = null) { if (!isOnConfirmationPage && (!instantTipMaxAmount || !instantTipEnabled || tipAmount > instantTipMaxAmount)) { setConfirmationPage(true); } else { const supportParams: SupportParams = { amount: tipAmount, claim_id: claimId || '', channel_id: (!incognito && activeChannelId) || undefined, }; // send tip/boost doSendTip(supportParams, isSupport); doHideModal(); } } // when the form button is clicked function handleSubmit() { if (!tipAmount || !claimId) return; if (setAmount) { setAmount(tipAmount); doHideModal(); return; } // send an instant tip (no need to go to an exchange first) if (instantTipEnabled && activeTab !== TAB_FIAT) { if (instantTipMax.currency === 'LBC') { sendSupportOrConfirm(instantTipMax.amount); } else { // Need to convert currency of instant purchase maximum before trying to send support Lbryio.getExchangeRates().then(({ LBC_USD }) => sendSupportOrConfirm(instantTipMax.amount / LBC_USD)); } // sending fiat tip } else if (activeTab === TAB_FIAT) { if (!isOnConfirmationPage) { setConfirmationPage(true); } else { const tipParams: TipParams = { tipAmount, tipChannelName: tipChannelName || '', channelClaimId: channelClaimId || '', }; const userParams: UserParams = { activeChannelName, activeChannelId }; // hit backend to send tip doSendCashTip( tipParams, !activeChannelId || incognito, userParams, claimId, stripeEnvironment, preferredCurrency ); doHideModal(); } // if it's a boost (?) } else { sendSupportOrConfirm(); } } function buildButtonText() { // test if frontend will show up as isNan function isNan(tipAmount) { // testing for NaN ES5 style https://stackoverflow.com/a/35912757/3973137 // also sometimes it's returned as a string // eslint-disable-next-line return tipAmount !== tipAmount || tipAmount === 'NaN'; } function convertToTwoDecimals(number) { return (Math.round(number * 100) / 100).toFixed(2); } const amountToShow = activeTab === TAB_FIAT ? convertToTwoDecimals(tipAmount) : tipAmount; // if it's a valid number display it, otherwise do an empty string const displayAmount = !isNan(tipAmount) ? amountToShow : ''; // build button text based on tab switch (activeTab) { case TAB_BOOST: return titleText; case TAB_FIAT: return __('Send a %amount% tip', { amount: `${fiatSymbolToUse}${displayAmount}` }); case TAB_LBC: return __('Send a %amount% tip', { amount: `${displayAmount} LBC` }); default: return titleText; } } React.useEffect(() => { if (!hasSelected && hasSelectedTab && activeTab !== hasSelectedTab) { setActiveTab(claimIsMine ? TAB_BOOST : hasSelectedTab); setSelected(true); } }, [activeTab, claimIsMine, hasSelected, hasSelectedTab, setActiveTab]); React.useEffect(() => { if (!hasSelectedTab && activeTab !== hasSelectedTab) { setPersistentTab(claimIsMine ? TAB_BOOST : activeTab); } }, [activeTab, claimIsMine, hasSelectedTab, setPersistentTab]); /** RENDER **/ const tabButtonProps = { isOnConfirmationPage, activeTab, setActiveTab }; let fiatIconToUse = ICONS.FINANCE; let fiatSymbolToUse = '$'; if (preferredCurrency === 'EUR') { fiatIconToUse = ICONS.EURO; fiatSymbolToUse = '€'; } return (
{/* if there is no LBC balance, show user frontend to get credits */} {/* if there is lbc, the main tip/boost gui with the 3 tabs at the top */} {!claimIsMine && (
{/* tip fiat tab button */} {stripeEnvironment && ( )} {/* tip LBC tab button */} {/* support LBC tab button */} {!isTipOnly && ( )}
)} {/* short explainer under the button */}
{explainerText}{' '} {/* {activeTab === TAB_FIAT && !hasCardSaved &&
} actions={ // confirmation modal, allow user to confirm or cancel transaction isOnConfirmationPage ? ( <>
{__('To --[the tip recipient]--')}
{channelName || title}
{__('From --[the tip sender]--')}
{(!incognito && activeChannelName) || __('Anonymous')}
{__('Amount')}
{activeTab === TAB_FIAT ? (

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

) : ( )}
) : !((activeTab === TAB_LBC || activeTab === TAB_BOOST) && balance === 0) ? ( <> {/* section to pick tip/boost amount */} setTipAmount(amount)} setDisableSubmitButton={setDisableSubmitButton} /> {/* send tip/boost button */}
) : ( // if it's LBC and there is no balance, you can prompt to purchase LBC }}>Supporting content requires %lbc% } subtitle={ }}> With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see. } actions={
} /> ) } /> ); } type TabButtonProps = { icon: string, label: string, name: string, isOnConfirmationPage: boolean, activeTab: string, setActiveTab: (string) => void, }; const TabSwitchButton = (tabButtonProps: TabButtonProps) => { const { icon, label, name, isOnConfirmationPage, activeTab, setActiveTab } = tabButtonProps; return (