Use modal for tip selection

This commit is contained in:
Rafael 2022-02-02 13:20:10 -03:00 committed by Thomas Zarebczan
parent b1c1263cca
commit ce11a4b9c1
9 changed files with 211 additions and 93 deletions

View file

@ -13,6 +13,7 @@ import { doSendTip, doSendCashTip } from 'redux/actions/wallet';
import { doToast } from 'redux/actions/notifications';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { selectSettingsByChannelId } from 'redux/selectors/comments';
import { doOpenModal } from 'redux/actions/app';
const select = (state, props) => {
const claim = selectClaimForUri(state, props.uri);
@ -48,6 +49,7 @@ const perform = (dispatch, ownProps) => ({
sendCashTip: (tipParams, userParams, claimId, environment, successCallback) =>
dispatch(doSendCashTip(tipParams, false, userParams, claimId, environment, successCallback)),
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
doOpenModal: (id, params) => dispatch(doOpenModal(id, params)),
});
export default connect(select, perform)(CommentCreate);

View file

@ -12,6 +12,7 @@ import { useHistory } from 'react-router';
import * as ICONS from 'constants/icons';
import * as KEYCODES from 'constants/keycodes';
import * as PAGES from 'constants/pages';
import * as MODALS from 'constants/modal_types';
import Button from 'component/button';
import ChannelThumbnail from 'component/channelThumbnail';
import classnames from 'classnames';
@ -29,6 +30,7 @@ import type { ElementRef } from 'react';
import UriIndicator from 'component/uriIndicator';
import usePersistedState from 'effects/use-persisted-state';
import WalletTipAmountSelector from 'component/walletTipAmountSelector';
import { useIsMobile } from 'effects/use-screensize';
import { getStripeEnvironment } from 'util/stripe';
const stripeEnvironment = getStripeEnvironment();
@ -67,6 +69,7 @@ type Props = {
sendTip: ({}, (any) => void, (any) => void) => void,
setQuickReply: (any) => void,
toast: (string) => void,
doOpenModal: (id: string, any) => void,
};
export function CommentCreate(props: Props) {
@ -96,8 +99,11 @@ export function CommentCreate(props: Props) {
sendCashTip,
sendTip,
setQuickReply,
doOpenModal,
} = props;
const isMobile = useIsMobile();
const formFieldRef: ElementRef<any> = React.useRef();
const buttonRef: ElementRef<any> = React.useRef();
@ -378,9 +384,13 @@ export function CommentCreate(props: Props) {
// Render
// **************************************************************************
const getActionButton = (title: string, label?: string, icon: string, handleClick: () => void) => (
<Button title={title} label={label} button="alt" icon={icon} onClick={handleClick} />
);
const getActionButton = (
title: string,
label?: string,
icon: string,
handleClick: () => void,
disabled?: boolean
) => <Button title={title} label={label} button="alt" icon={icon} onClick={handleClick} disabled={disabled} />;
if (channelSettings && !channelSettings.comments_enabled) {
return <Empty padded text={__('This channel has disabled comments on their page.')} />;
@ -498,11 +508,11 @@ export function CommentCreate(props: Props) {
</>
)}
{(isSupportComment || (isReviewingStickerComment && stickerPrice)) && (
{!isMobile && (isSupportComment || (isReviewingStickerComment && stickerPrice)) && (
<WalletTipAmountSelector
activeTab={activeTab}
amount={tipAmount}
claim={claim}
uri={uri}
convertedAmount={convertedAmount}
customTipAmount={stickerPrice}
exchangeRate={exchangeRate}
@ -603,9 +613,23 @@ export function CommentCreate(props: Props) {
isSupportComment ? __('Switch to Credits') : undefined,
ICONS.LBC,
() => {
setIsSupportComment(true);
setActiveTab(TAB_LBC);
}
if (isMobile) {
doOpenModal(MODALS.SEND_TIP, {
uri,
isTipOnly: true,
hasSelectedTab: TAB_LBC,
setAmount: (amount) => {
setTipAmount(amount);
setReviewingSupportComment(true);
},
});
} else {
setIsSupportComment(true);
}
},
!commentValue.length
)}
{stripeEnvironment &&
@ -615,9 +639,23 @@ export function CommentCreate(props: Props) {
isSupportComment ? __('Switch to Cash') : undefined,
ICONS.FINANCE,
() => {
setIsSupportComment(true);
setActiveTab(TAB_FIAT);
}
if (isMobile) {
doOpenModal(MODALS.SEND_TIP, {
uri,
isTipOnly: true,
hasSelectedTab: TAB_FIAT,
setAmount: (amount) => {
setTipAmount(amount);
setReviewingSupportComment(true);
},
});
} else {
setIsSupportComment(true);
}
},
!commentValue.length
)}
</>
)}

View file

@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import {
selectTitleForUri,
makeSelectClaimForUri,
selectClaimForUri,
selectClaimIsMineForUri,
selectFetchingMyChannels,
} from 'redux/selectors/claims';
@ -12,19 +12,44 @@ import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
import { selectBalance, selectIsSendingSupport } from 'redux/selectors/wallet';
import { withRouter } from 'react-router';
import * as SETTINGS from 'constants/settings';
import { getChannelIdFromClaim, getChannelNameFromClaim } from 'util/claim';
import WalletSendTip from './view';
const select = (state, props) => ({
activeChannelClaim: selectActiveChannelClaim(state),
balance: selectBalance(state),
claim: makeSelectClaimForUri(props.uri, false)(state),
claimIsMine: selectClaimIsMineForUri(state, props.uri),
fetchingChannels: selectFetchingMyChannels(state),
incognito: selectIncognito(state),
instantTipEnabled: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_ENABLED),
instantTipMax: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_MAX),
isPending: selectIsSendingSupport(state),
title: selectTitleForUri(state, props.uri),
});
const select = (state, props) => {
const { uri } = props;
export default withRouter(connect(select, { doHideModal, doSendTip, doSendCashTip })(WalletSendTip));
const claim = selectClaimForUri(state, uri, false);
const { claim_id: claimId, value_type: claimType } = claim || {};
// setup variables for backend tip API
const channelClaimId = getChannelIdFromClaim(claim);
const tipChannelName = getChannelNameFromClaim(claim);
const activeChannelClaim = selectActiveChannelClaim(state);
const { name: activeChannelName, claim_id: activeChannelId } = activeChannelClaim || {};
return {
activeChannelName,
activeChannelId,
balance: selectBalance(state),
claimId,
claimType,
channelClaimId,
tipChannelName,
claimIsMine: selectClaimIsMineForUri(state, uri),
fetchingChannels: selectFetchingMyChannels(state),
incognito: selectIncognito(state),
instantTipEnabled: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_ENABLED),
instantTipMax: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_MAX),
isPending: selectIsSendingSupport(state),
title: selectTitleForUri(state, uri),
};
};
const perform = {
doHideModal,
doSendTip,
doSendCashTip,
};
export default withRouter(connect(select, perform)(WalletSendTip));

View file

@ -20,14 +20,19 @@ 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 = {
activeChannelClaim: ?ChannelClaim,
activeChannelId?: string,
activeChannelName?: string,
balance: number,
claim: StreamClaim,
claimId?: string,
claimType?: string,
channelClaimId?: string,
tipChannelName?: string,
claimIsMine: boolean,
fetchingChannels: boolean,
incognito: boolean,
@ -37,16 +42,23 @@ type Props = {
isSupport: boolean,
title: string,
uri: string,
isTipOnly?: boolean,
hasSelectedTab?: string,
doHideModal: () => void,
doSendCashTip: (TipParams, boolean, UserParams, string, ?string) => string,
doSendTip: (SupportParams, boolean) => void, // function that comes from lbry-redux
setAmount?: (number) => void,
};
function WalletSendTip(props: Props) {
export default function WalletSendTip(props: Props) {
const {
activeChannelClaim,
activeChannelId,
activeChannelName,
balance,
claim = {},
claimId,
claimType,
channelClaimId,
tipChannelName,
claimIsMine,
fetchingChannels,
incognito,
@ -55,33 +67,22 @@ function WalletSendTip(props: Props) {
isPending,
title,
uri,
isTipOnly,
hasSelectedTab,
doHideModal,
doSendCashTip,
doSendTip,
setAmount,
} = props;
/** WHAT TAB TO SHOW **/
// set default tab to for new users based on if it's their claim or not
let defaultTabToShow;
if (claimIsMine) {
defaultTabToShow = TAB_BOOST;
} else {
defaultTabToShow = TAB_LBC;
}
const defaultTabToShow = claimIsMine ? TAB_BOOST : TAB_LBC;
// loads the default tab if nothing else is there yet
const [activeTab, setActiveTab] = usePersistedState(defaultTabToShow);
// if a broken default is set, set it to the proper default
if (activeTab !== TAB_BOOST && activeTab !== TAB_LBC && activeTab !== TAB_FIAT) {
// if the claim is the user's set it to boost
setActiveTab(defaultTabToShow);
}
// if the claim is yours but the active tab is not boost, change it to boost
if (claimIsMine && activeTab !== TAB_BOOST) {
setActiveTab(TAB_BOOST);
}
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);
@ -92,20 +93,14 @@ function WalletSendTip(props: Props) {
/** CONSTS **/
const claimTypeText = getClaimTypeText();
const isSupport = claimIsMine || activeTab === TAB_BOOST;
const titleText = claimIsMine
? __('Boost Your %claimTypeText%', { claimTypeText })
: __('Boost This %claimTypeText%', { claimTypeText });
const { claim_id: claimId } = claim;
const titleText = isSupport
? __(claimIsMine ? 'Boost Your %claimTypeText%' : 'Boost This %claimTypeText%', { claimTypeText })
: __('Tip This %claimTypeText%', { claimTypeText });
let channelName;
try {
({ channelName } = parseURI(uri));
} catch (e) {}
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
// setup variables for backend tip API
const channelClaimId = claim.signing_channel ? claim.signing_channel.claim_id : claim.claim_id;
const tipChannelName = claim.signing_channel ? claim.signing_channel.name : claim.name;
// icon to use or explainer text to show per tab
let explainerText = '',
@ -131,7 +126,7 @@ function WalletSendTip(props: Props) {
/** FUNCTIONS **/
function getClaimTypeText() {
switch (claim.value_type) {
switch (claimType) {
case 'stream':
return __('Content');
case 'channel':
@ -152,8 +147,8 @@ function WalletSendTip(props: Props) {
} else {
const supportParams: SupportParams = {
amount: tipAmount,
claim_id: claimId,
channel_id: activeChannelClaim && !incognito ? activeChannelClaim.claim_id : undefined,
claim_id: claimId || '',
channel_id: (!incognito && activeChannelId) || undefined,
};
// send tip/boost
@ -166,6 +161,12 @@ function WalletSendTip(props: Props) {
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') {
@ -179,11 +180,15 @@ function WalletSendTip(props: Props) {
if (!isOnConfirmationPage) {
setConfirmationPage(true);
} else {
const tipParams: TipParams = { tipAmount, tipChannelName, channelClaimId };
const tipParams: TipParams = {
tipAmount,
tipChannelName: tipChannelName || '',
channelClaimId: channelClaimId || '',
};
const userParams: UserParams = { activeChannelName, activeChannelId };
// hit backend to send tip
doSendCashTip(tipParams, !activeChannelClaim || incognito, userParams, claimId, stripeEnvironment);
doSendCashTip(tipParams, !activeChannelId || incognito, userParams, claimId, stripeEnvironment);
doHideModal();
}
// if it's a boost (?)
@ -221,22 +226,22 @@ function WalletSendTip(props: Props) {
}
}
React.useEffect(() => {
if (!hasSelected && hasSelectedTab && activeTab !== hasSelectedTab) {
setActiveTab(hasSelectedTab);
setSelected(true);
}
}, [activeTab, hasSelected, hasSelectedTab, setActiveTab]);
React.useEffect(() => {
if (!hasSelectedTab && activeTab !== hasSelectedTab) {
setPersistentTab(activeTab);
}
}, [activeTab, hasSelectedTab, setPersistentTab]);
/** RENDER **/
const getTabButton = (tabIcon: string, tabLabel: string, tabName: string) => (
<Button
key={tabName}
icon={tabIcon}
label={tabLabel}
button="alt"
onClick={() => {
const tipInputElement = document.getElementById('tip-input');
if (tipInputElement) tipInputElement.focus();
if (!isOnConfirmationPage) setActiveTab(tabName);
}}
className={classnames('button-toggle', { 'button-toggle--active': activeTab === tabName })}
/>
);
const tabButtonProps = { isOnConfirmationPage, activeTab, setActiveTab };
return (
<Form onSubmit={handleSubmit}>
@ -249,13 +254,17 @@ function WalletSendTip(props: Props) {
{!claimIsMine && (
<div className="section">
{/* tip LBC tab button */}
{getTabButton(ICONS.LBC, __('Tip'), TAB_LBC)}
<TabSwitchButton icon={ICONS.LBC} label={__('Tip')} name={TAB_LBC} {...tabButtonProps} />
{/* tip fiat tab button */}
{stripeEnvironment && getTabButton(ICONS.FINANCE, __('Tip'), TAB_FIAT)}
{stripeEnvironment && (
<TabSwitchButton icon={ICONS.FINANCE} label={__('Tip')} name={TAB_FIAT} {...tabButtonProps} />
)}
{/* support LBC tab button */}
{getTabButton(ICONS.TRENDING, __('Boost'), TAB_BOOST)}
{!isTipOnly && (
<TabSwitchButton icon={ICONS.TRENDING} label={__('Boost')} name={TAB_BOOST} {...tabButtonProps} />
)}
</div>
)}
@ -280,9 +289,7 @@ function WalletSendTip(props: Props) {
<div className="confirm__label">{__('To --[the tip recipient]--')}</div>
<div className="confirm__value">{channelName || title}</div>
<div className="confirm__label">{__('From --[the tip sender]--')}</div>
<div className="confirm__value">
{activeChannelClaim && !incognito ? activeChannelClaim.name : __('Anonymous')}
</div>
<div className="confirm__value">{(!incognito && activeChannelName) || __('Anonymous')}</div>
<div className="confirm__label">{confirmLabel}</div>
<div className="confirm__value">
{activeTab === TAB_FIAT ? (
@ -306,7 +313,7 @@ function WalletSendTip(props: Props) {
<WalletTipAmountSelector
setTipError={setTipError}
tipError={tipError}
claim={claim}
uri={uri}
activeTab={activeTab === TAB_BOOST ? TAB_LBC : activeTab}
amount={tipAmount}
onChange={(amount) => setTipAmount(amount)}
@ -366,4 +373,29 @@ function WalletSendTip(props: Props) {
);
}
export default WalletSendTip;
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 (
<Button
key={name}
icon={icon}
label={label}
button="alt"
onClick={() => {
const tipInputElement = document.getElementById('tip-input');
if (tipInputElement) tipInputElement.focus();
if (!isOnConfirmationPage) setActiveTab(name);
}}
className={classnames('button-toggle', { 'button-toggle--active': activeTab === name })}
/>
);
};

View file

@ -1,7 +1,11 @@
import { connect } from 'react-redux';
import { selectBalance } from 'redux/selectors/wallet';
import { selectClaimForUri } from 'redux/selectors/claims';
import WalletTipAmountSelector from './view';
const select = (state) => ({ balance: selectBalance(state) });
const select = (state, props) => ({
balance: selectBalance(state),
claim: selectClaimForUri(state, props.uri),
});
export default connect(select)(WalletTipAmountSelector);

View file

@ -74,8 +74,8 @@ function WalletTipAmountSelector(props: Props) {
if (setDisableSubmitButton) setDisableSubmitButton(shouldDisableFiatSelectors);
// setup variables for tip API
const channelClaimId = claim.signing_channel ? claim.signing_channel.claim_id : claim.claim_id;
const tipChannelName = claim.signing_channel ? claim.signing_channel.name : claim.name;
const channelClaimId = claim ? (claim.signing_channel ? claim.signing_channel.claim_id : claim.claim_id) : undefined;
const tipChannelName = claim ? (claim.signing_channel ? claim.signing_channel.name : claim.name) : undefined;
/**
* whether tip amount selection/review functionality should be disabled
@ -220,6 +220,8 @@ function WalletTipAmountSelector(props: Props) {
}
}, [activeTab, amount, balance, convertedAmount, customTipAmount, exchangeRate, setTipError]);
if (!claim) return null;
const getHelpMessage = (helpMessage: any) => <div className="help">{helpMessage}</div>;
return (

View file

@ -2,8 +2,8 @@ import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import ModalSendTip from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
});
const perform = {
doHideModal,
};
export default connect(null, perform)(ModalSendTip);

View file

@ -4,19 +4,29 @@ import { Modal } from 'modal/modal';
import SendTip from 'component/walletSendTip';
type Props = {
closeModal: () => void,
uri: string,
claimIsMine: boolean,
isSupport: boolean,
isTipOnly?: boolean,
hasSelectedTab?: string,
doHideModal: () => void,
setAmount?: (number) => void,
};
class ModalSendTip extends React.PureComponent<Props> {
render() {
const { closeModal, uri, claimIsMine } = this.props;
const { uri, claimIsMine, isTipOnly, hasSelectedTab, doHideModal, setAmount } = this.props;
return (
<Modal onAborted={closeModal} isOpen type="card">
<SendTip uri={uri} claimIsMine={claimIsMine} onCancel={closeModal} />
<Modal onAborted={doHideModal} isOpen type="card">
<SendTip
uri={uri}
claimIsMine={claimIsMine}
onCancel={doHideModal}
isTipOnly={isTipOnly}
hasSelectedTab={hasSelectedTab}
setAmount={setAmount}
/>
</Modal>
);
}

View file

@ -99,6 +99,11 @@ export function getChannelIdFromClaim(claim: ?Claim) {
}
}
export function getChannelNameFromClaim(claim: ?Claim) {
const channelFromClaim = getChannelFromClaim(claim);
return channelFromClaim && channelFromClaim.name;
}
export function getChannelFromClaim(claim: ?Claim) {
return !claim
? null