Automatically claim initial rewards (new_user & email_verified) when … #6807

Merged
Ruk33 merged 2 commits from 6788-automatically-claim-initial-rewards into master 2021-08-18 18:34:25 +02:00
6 changed files with 110 additions and 42 deletions

View file

@ -17,6 +17,8 @@ import {
} from 'lbry-redux'; } from 'lbry-redux';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments'; import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
import { doClaimInitialRewards } from 'redux/actions/rewards';
import { selectIsClaimingInitialRewards, selectHasClaimedInitialRewards } from 'redux/selectors/rewards';
import ChannelForm from './view'; import ChannelForm from './view';
const select = (state, props) => ({ const select = (state, props) => ({
@ -36,6 +38,8 @@ const select = (state, props) => ({
createError: selectCreateChannelError(state), createError: selectCreateChannelError(state),
creatingChannel: selectCreatingChannel(state), creatingChannel: selectCreatingChannel(state),
balance: selectBalance(state), balance: selectBalance(state),
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
@ -50,6 +54,7 @@ const perform = (dispatch) => ({
); );
}, },
clearChannelErrors: () => dispatch(doClearChannelErrors()), clearChannelErrors: () => dispatch(doClearChannelErrors()),
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
}); });
export default connect(select, perform)(ChannelForm); export default connect(select, perform)(ChannelForm);

View file

@ -48,6 +48,7 @@ type Props = {
createError: string, createError: string,
creatingChannel: boolean, creatingChannel: boolean,
clearChannelErrors: () => void, clearChannelErrors: () => void,
claimInitialRewards: () => void,
onDone: () => void, onDone: () => void,
openModal: ( openModal: (
id: string, id: string,
@ -55,6 +56,8 @@ type Props = {
) => void, ) => void,
uri: string, uri: string,
disabled: boolean, disabled: boolean,
isClaimingInitialRewards: boolean,
hasClaimedInitialRewards: boolean,
}; };
function ChannelForm(props: Props) { function ChannelForm(props: Props) {
@ -79,8 +82,11 @@ function ChannelForm(props: Props) {
creatingChannel, creatingChannel,
createError, createError,
clearChannelErrors, clearChannelErrors,
claimInitialRewards,
openModal, openModal,
disabled, disabled,
isClaimingInitialRewards,
hasClaimedInitialRewards,
} = props; } = props;
const [nameError, setNameError] = React.useState(undefined); const [nameError, setNameError] = React.useState(undefined);
const [bidError, setBidError] = React.useState(''); const [bidError, setBidError] = React.useState('');
@ -94,6 +100,22 @@ function ChannelForm(props: Props) {
const languageParam = params.languages; const languageParam = params.languages;
const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0]; const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0];
const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1]; const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1];
const submitLabel = React.useMemo(() => {
if (isClaimingInitialRewards) {
return __('Claiming credits...');
}
return creatingChannel || updatingChannel ? __('Submitting') : __('Submit');
}, [isClaimingInitialRewards, creatingChannel, updatingChannel]);
const submitDisabled = React.useMemo(() => {
return (
isClaimingInitialRewards ||
creatingChannel ||
updatingChannel ||
nameError ||
bidError ||
(isNewChannel && !params.name)
);
}, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params]);
function getChannelParams() { function getChannelParams() {
// fill this in with sdk data // fill this in with sdk data
@ -219,6 +241,12 @@ function ChannelForm(props: Props) {
clearChannelErrors(); clearChannelErrors();
}, [clearChannelErrors]); }, [clearChannelErrors]);
React.useEffect(() => {
if (!hasClaimedInitialRewards) {
claimInitialRewards();
}
}, [hasClaimedInitialRewards, claimInitialRewards]);
// TODO clear and bail after submit // TODO clear and bail after submit
return ( return (
<> <>
@ -453,14 +481,7 @@ function ChannelForm(props: Props) {
actions={ actions={
<> <>
<div className="section__actions"> <div className="section__actions">
<Button <Button button="primary" disabled={submitDisabled} label={submitLabel} onClick={handleSubmit} />
button="primary"
disabled={
creatingChannel || updatingChannel || nameError || bidError || (isNewChannel && !params.name)
}
label={creatingChannel || updatingChannel ? __('Submitting') : __('Submit')}
onClick={handleSubmit}
/>
<Button button="link" label={__('Cancel')} onClick={onDone} /> <Button button="link" label={__('Cancel')} onClick={onDone} />
</div> </div>
{errorMsg ? ( {errorMsg ? (

View file

@ -18,7 +18,12 @@ import {
} from 'lbry-redux'; } from 'lbry-redux';
import * as RENDER_MODES from 'constants/file_render_modes'; import * as RENDER_MODES from 'constants/file_render_modes';
import { doPublishDesktop } from 'redux/actions/publish'; import { doPublishDesktop } from 'redux/actions/publish';
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards'; import { doClaimInitialRewards } from 'redux/actions/rewards';
import {
selectUnclaimedRewardValue,
selectIsClaimingInitialRewards,
selectHasClaimedInitialRewards,
} from 'redux/selectors/rewards';
import { import {
selectModal, selectModal,
selectActiveChannelClaim, selectActiveChannelClaim,
@ -27,8 +32,8 @@ import {
} from 'redux/selectors/app'; } from 'redux/selectors/app';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content'; import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
import PublishPage from './view';
import { selectUser } from 'redux/selectors/user'; import { selectUser } from 'redux/selectors/user';
import PublishPage from './view';
const select = (state) => { const select = (state) => {
const myClaimForUri = selectMyClaimForUri(state); const myClaimForUri = selectMyClaimForUri(state);
@ -59,6 +64,8 @@ const select = (state) => {
myChannels: selectMyChannelClaims(state), myChannels: selectMyChannelClaims(state),
incognito: selectIncognito(state), incognito: selectIncognito(state),
activeChannelStakedLevel: selectActiveChannelStakedLevel(state), activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
isClaimingInitialRewards: selectIsClaimingInitialRewards(state),
hasClaimedInitialRewards: selectHasClaimedInitialRewards(state),
}; };
}; };
@ -70,6 +77,7 @@ const perform = (dispatch) => ({
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)), prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()), resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
checkAvailability: (name) => dispatch(doCheckPublishNameAvailability(name)), checkAvailability: (name) => dispatch(doCheckPublishNameAvailability(name)),
claimInitialRewards: () => dispatch(doClaimInitialRewards()),
}); });
export default connect(select, perform)(PublishPage); export default connect(select, perform)(PublishPage);

View file

@ -90,6 +90,9 @@ type Props = {
isPostClaim: boolean, isPostClaim: boolean,
permanentUrl: ?string, permanentUrl: ?string,
remoteUrl: ?string, remoteUrl: ?string,
isClaimingInitialRewards: boolean,
claimInitialRewards: () => void,
hasClaimedInitialRewards: boolean,
}; };
function PublishForm(props: Props) { function PublishForm(props: Props) {
@ -128,6 +131,9 @@ function PublishForm(props: Props) {
isPostClaim, isPostClaim,
permanentUrl, permanentUrl,
remoteUrl, remoteUrl,
isClaimingInitialRewards,
claimInitialRewards,
hasClaimedInitialRewards,
} = props; } = props;
const { replace, location } = useHistory(); const { replace, location } = useHistory();
@ -263,6 +269,12 @@ function PublishForm(props: Props) {
} }
}, [activeChannelClaimStr, setSignedMessage]); }, [activeChannelClaimStr, setSignedMessage]);
useEffect(() => {
if (!hasClaimedInitialRewards) {
claimInitialRewards();
}
}, [hasClaimedInitialRewards, claimInitialRewards]);
useEffect(() => { useEffect(() => {
if (!modal) { if (!modal) {
setTimeout(() => { setTimeout(() => {
@ -298,7 +310,10 @@ function PublishForm(props: Props) {
const isLivestreamMode = mode === PUBLISH_MODES.LIVESTREAM; const isLivestreamMode = mode === PUBLISH_MODES.LIVESTREAM;
let submitLabel; let submitLabel;
if (publishing) {
if (isClaimingInitialRewards) {
submitLabel = __('Claiming credits...');
} else if (publishing) {
if (isStillEditing) { if (isStillEditing) {
submitLabel = __('Saving...'); submitLabel = __('Saving...');
} else if (isLivestreamMode) { } else if (isLivestreamMode) {
@ -623,6 +638,7 @@ function PublishForm(props: Props) {
onClick={handlePublish} onClick={handlePublish}
label={submitLabel} label={submitLabel}
disabled={ disabled={
isClaimingInitialRewards ||
formDisabled || formDisabled ||
!formValid || !formValid ||
uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS ||

View file

@ -6,13 +6,13 @@ import { doFetchInviteStatus } from 'redux/actions/user';
import rewards from 'rewards'; import rewards from 'rewards';
export function doRewardList() { export function doRewardList() {
return dispatch => { return (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.FETCH_REWARDS_STARTED, type: ACTIONS.FETCH_REWARDS_STARTED,
}); });
Lbryio.call('reward', 'list', { multiple_rewards_per_type: true }) Lbryio.call('reward', 'list', { multiple_rewards_per_type: true })
.then(userRewards => { .then((userRewards) => {
dispatch({ dispatch({
type: ACTIONS.FETCH_REWARDS_COMPLETED, type: ACTIONS.FETCH_REWARDS_COMPLETED,
data: { userRewards }, data: { userRewards },
@ -35,7 +35,7 @@ export function doClaimRewardType(rewardType, options = {}) {
const reward = const reward =
rewardType === rewards.TYPE_REWARD_CODE || rewardType === rewards.TYPE_NEW_ANDROID rewardType === rewards.TYPE_REWARD_CODE || rewardType === rewards.TYPE_NEW_ANDROID
? { reward_type: rewards.TYPE_REWARD_CODE } ? { reward_type: rewards.TYPE_REWARD_CODE }
: unclaimedRewards.find(ur => ur.reward_type === rewardType); : unclaimedRewards.find((ur) => ur.reward_type === rewardType);
// Try to claim the email reward right away, even if we haven't called reward_list yet // Try to claim the email reward right away, even if we haven't called reward_list yet
if ( if (
@ -74,7 +74,7 @@ export function doClaimRewardType(rewardType, options = {}) {
data: { reward }, data: { reward },
}); });
const success = successReward => { const success = (successReward) => {
// Temporary timeout to ensure the sdk has the correct balance after claiming a reward // Temporary timeout to ensure the sdk has the correct balance after claiming a reward
setTimeout(() => { setTimeout(() => {
dispatch(doUpdateBalance()).then(() => { dispatch(doUpdateBalance()).then(() => {
@ -99,7 +99,7 @@ export function doClaimRewardType(rewardType, options = {}) {
}, 2000); }, 2000);
}; };
const failure = error => { const failure = (error) => {
dispatch({ dispatch({
type: ACTIONS.CLAIM_REWARD_FAILURE, type: ACTIONS.CLAIM_REWARD_FAILURE,
data: { data: {
@ -121,6 +121,13 @@ export function doClaimRewardType(rewardType, options = {}) {
}; };
} }
export function doClaimInitialRewards() {
return (dispatch) => {
dispatch(doClaimRewardType(rewards.TYPE_NEW_USER));
dispatch(doClaimRewardType(rewards.TYPE_CONFIRM_EMAIL));
};
}
export function doClaimEligiblePurchaseRewards() { export function doClaimEligiblePurchaseRewards() {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
@ -131,10 +138,10 @@ export function doClaimEligiblePurchaseRewards() {
return; return;
} }
if (unclaimedRewards.find(ur => ur.reward_type === rewards.TYPE_FIRST_STREAM)) { if (unclaimedRewards.find((ur) => ur.reward_type === rewards.TYPE_FIRST_STREAM)) {
dispatch(doClaimRewardType(rewards.TYPE_FIRST_STREAM)); dispatch(doClaimRewardType(rewards.TYPE_FIRST_STREAM));
} else { } else {
[rewards.TYPE_MANY_DOWNLOADS, rewards.TYPE_DAILY_VIEW].forEach(type => { [rewards.TYPE_MANY_DOWNLOADS, rewards.TYPE_DAILY_VIEW].forEach((type) => {
dispatch(doClaimRewardType(type, { failSilently: true })); dispatch(doClaimRewardType(type, { failSilently: true }));
}); });
} }
@ -142,7 +149,7 @@ export function doClaimEligiblePurchaseRewards() {
} }
export function doClaimRewardClearError(reward) { export function doClaimRewardClearError(reward) {
return dispatch => { return (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.CLAIM_REWARD_CLEAR_ERROR, type: ACTIONS.CLAIM_REWARD_CLEAR_ERROR,
data: { reward }, data: { reward },
@ -151,8 +158,8 @@ export function doClaimRewardClearError(reward) {
} }
export function doFetchRewardedContent() { export function doFetchRewardedContent() {
return dispatch => { return (dispatch) => {
const success = nameToClaimId => { const success = (nameToClaimId) => {
dispatch({ dispatch({
type: ACTIONS.FETCH_REWARD_CONTENT_COMPLETED, type: ACTIONS.FETCH_REWARD_CONTENT_COMPLETED,
data: { data: {

View file

@ -1,15 +1,15 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import REWARDS from 'rewards'; import REWARDS from 'rewards';
const selectState = state => state.rewards || {}; const selectState = (state) => state.rewards || {};
export const selectUnclaimedRewardsByType = createSelector(selectState, state => state.unclaimedRewardsByType); export const selectUnclaimedRewardsByType = createSelector(selectState, (state) => state.unclaimedRewardsByType);
export const selectClaimedRewardsById = createSelector(selectState, state => state.claimedRewardsById); export const selectClaimedRewardsById = createSelector(selectState, (state) => state.claimedRewardsById);
export const selectClaimedRewards = createSelector(selectClaimedRewardsById, byId => Object.values(byId) || []); export const selectClaimedRewards = createSelector(selectClaimedRewardsById, (byId) => Object.values(byId) || []);
export const selectClaimedRewardsByTransactionId = createSelector(selectClaimedRewards, rewards => export const selectClaimedRewardsByTransactionId = createSelector(selectClaimedRewards, (rewards) =>
rewards.reduce((mapParam, reward) => { rewards.reduce((mapParam, reward) => {
const map = mapParam; const map = mapParam;
map[reward.transaction_id] = reward; map[reward.transaction_id] = reward;
@ -17,47 +17,58 @@ export const selectClaimedRewardsByTransactionId = createSelector(selectClaimedR
}, {}) }, {})
); );
export const selectUnclaimedRewards = createSelector(selectState, state => state.unclaimedRewards); export const selectUnclaimedRewards = createSelector(selectState, (state) => state.unclaimedRewards);
export const selectFetchingRewards = createSelector(selectState, state => !!state.fetching); export const selectFetchingRewards = createSelector(selectState, (state) => !!state.fetching);
export const selectUnclaimedRewardValue = createSelector(selectUnclaimedRewards, rewards => export const selectUnclaimedRewardValue = createSelector(selectUnclaimedRewards, (rewards) =>
rewards.reduce((sum, reward) => sum + reward.reward_amount, 0) rewards.reduce((sum, reward) => sum + reward.reward_amount, 0)
); );
export const selectClaimsPendingByType = createSelector(selectState, state => state.claimPendingByType); export const selectClaimsPendingByType = createSelector(selectState, (state) => state.claimPendingByType);
const selectIsClaimRewardPending = (state, props) => selectClaimsPendingByType(state, props)[props.reward_type]; const selectIsClaimRewardPending = (state, props) => selectClaimsPendingByType(state, props)[props.reward_type];
export const makeSelectIsRewardClaimPending = () => export const makeSelectIsRewardClaimPending = () =>
createSelector(selectIsClaimRewardPending, isClaiming => isClaiming); createSelector(selectIsClaimRewardPending, (isClaiming) => isClaiming);
export const selectClaimErrorsByType = createSelector(selectState, state => state.claimErrorsByType); export const selectClaimErrorsByType = createSelector(selectState, (state) => state.claimErrorsByType);
const selectClaimRewardError = (state, props) => selectClaimErrorsByType(state, props)[props.reward_type]; const selectClaimRewardError = (state, props) => selectClaimErrorsByType(state, props)[props.reward_type];
export const makeSelectClaimRewardError = () => createSelector(selectClaimRewardError, errorMessage => errorMessage); export const makeSelectClaimRewardError = () => createSelector(selectClaimRewardError, (errorMessage) => errorMessage);
const selectRewardByType = (state, rewardType) => const selectRewardByType = (state, rewardType) =>
selectUnclaimedRewards(state).find(reward => reward.reward_type === rewardType); selectUnclaimedRewards(state).find((reward) => reward.reward_type === rewardType);
export const makeSelectRewardByType = () => createSelector(selectRewardByType, reward => reward); export const makeSelectRewardByType = () => createSelector(selectRewardByType, (reward) => reward);
const selectRewardByClaimCode = (state, claimCode) => const selectRewardByClaimCode = (state, claimCode) =>
selectUnclaimedRewards(state).find(reward => reward.claim_code === claimCode); selectUnclaimedRewards(state).find((reward) => reward.claim_code === claimCode);
export const makeSelectRewardByClaimCode = () => createSelector(selectRewardByClaimCode, reward => reward); export const makeSelectRewardByClaimCode = () => createSelector(selectRewardByClaimCode, (reward) => reward);
export const makeSelectRewardAmountByType = () => export const makeSelectRewardAmountByType = () =>
createSelector(selectRewardByType, reward => (reward ? reward.reward_amount : 0)); createSelector(selectRewardByType, (reward) => (reward ? reward.reward_amount : 0));
export const selectRewardContentClaimIds = createSelector(selectState, state => state.rewardedContentClaimIds); export const selectRewardContentClaimIds = createSelector(selectState, (state) => state.rewardedContentClaimIds);
export const selectReferralReward = createSelector( export const selectReferralReward = createSelector(
selectUnclaimedRewards, selectUnclaimedRewards,
unclaimedRewards => unclaimedRewards.filter(reward => reward.reward_type === REWARDS.TYPE_REFERRAL)[0] (unclaimedRewards) => unclaimedRewards.filter((reward) => reward.reward_type === REWARDS.TYPE_REFERRAL)[0]
); );
export const selectHasUnclaimedRefereeReward = createSelector(selectUnclaimedRewards, unclaimedRewards => export const selectHasUnclaimedRefereeReward = createSelector(selectUnclaimedRewards, (unclaimedRewards) =>
unclaimedRewards.some(reward => reward.reward_type === REWARDS.TYPE_REFEREE) unclaimedRewards.some((reward) => reward.reward_type === REWARDS.TYPE_REFEREE)
); );
export const selectIsClaimingInitialRewards = createSelector(selectClaimsPendingByType, (claimsPendingByType) => {
return !!(claimsPendingByType[REWARDS.TYPE_NEW_USER] || claimsPendingByType[REWARDS.TYPE_CONFIRM_EMAIL]);
});
export const selectHasClaimedInitialRewards = createSelector(selectClaimedRewardsById, (claimedRewardsById) => {
const claims = Object.values(claimedRewardsById);
const newUserClaimed = !!claims.find((claim) => claim && claim.reward_type === REWARDS.TYPE_NEW_USER);
const confirmEmailClaimed = !!claims.find((claim) => claim && claim.reward_type === REWARDS.TYPE_CONFIRM_EMAIL);
return newUserClaimed && confirmEmailClaimed;
});