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

View file

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

View file

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

View file

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

View file

@ -6,13 +6,13 @@ import { doFetchInviteStatus } from 'redux/actions/user';
import rewards from 'rewards';
export function doRewardList() {
return dispatch => {
return (dispatch) => {
dispatch({
type: ACTIONS.FETCH_REWARDS_STARTED,
});
Lbryio.call('reward', 'list', { multiple_rewards_per_type: true })
.then(userRewards => {
.then((userRewards) => {
dispatch({
type: ACTIONS.FETCH_REWARDS_COMPLETED,
data: { userRewards },
@ -35,7 +35,7 @@ export function doClaimRewardType(rewardType, options = {}) {
const reward =
rewardType === rewards.TYPE_REWARD_CODE || rewardType === rewards.TYPE_NEW_ANDROID
? { 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
if (
@ -74,7 +74,7 @@ export function doClaimRewardType(rewardType, options = {}) {
data: { reward },
});
const success = successReward => {
const success = (successReward) => {
// Temporary timeout to ensure the sdk has the correct balance after claiming a reward
setTimeout(() => {
dispatch(doUpdateBalance()).then(() => {
@ -99,7 +99,7 @@ export function doClaimRewardType(rewardType, options = {}) {
}, 2000);
};
const failure = error => {
const failure = (error) => {
dispatch({
type: ACTIONS.CLAIM_REWARD_FAILURE,
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() {
return (dispatch, getState) => {
const state = getState();
@ -131,10 +138,10 @@ export function doClaimEligiblePurchaseRewards() {
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));
} 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 }));
});
}
@ -142,7 +149,7 @@ export function doClaimEligiblePurchaseRewards() {
}
export function doClaimRewardClearError(reward) {
return dispatch => {
return (dispatch) => {
dispatch({
type: ACTIONS.CLAIM_REWARD_CLEAR_ERROR,
data: { reward },
@ -151,8 +158,8 @@ export function doClaimRewardClearError(reward) {
}
export function doFetchRewardedContent() {
return dispatch => {
const success = nameToClaimId => {
return (dispatch) => {
const success = (nameToClaimId) => {
dispatch({
type: ACTIONS.FETCH_REWARD_CONTENT_COMPLETED,
data: {

View file

@ -1,15 +1,15 @@
import { createSelector } from 'reselect';
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) => {
const map = mapParam;
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)
);
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];
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];
export const makeSelectClaimRewardError = () => createSelector(selectClaimRewardError, errorMessage => errorMessage);
export const makeSelectClaimRewardError = () => createSelector(selectClaimRewardError, (errorMessage) => errorMessage);
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) =>
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 = () =>
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(
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 =>
unclaimedRewards.some(reward => reward.reward_type === REWARDS.TYPE_REFEREE)
export const selectHasUnclaimedRefereeReward = createSelector(selectUnclaimedRewards, (unclaimedRewards) =>
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;
});