Merge pull request #1509 from lbryio/issue/1329

Rewards reorder and snackbar notifications
This commit is contained in:
Sean Yesmunt 2018-05-31 00:46:51 -04:00 committed by GitHub
commit d65d92e0b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 41 additions and 91 deletions

View file

@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313)) * Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
* Made font in price badge larger ([#1420](https://github.com/lbryio/lbry-app/pull/1420)) * Made font in price badge larger ([#1420](https://github.com/lbryio/lbry-app/pull/1420))
* Store subscriptions in internal database ([#1424](https://github.com/lbryio/lbry-app/pull/1424)) * Store subscriptions in internal database ([#1424](https://github.com/lbryio/lbry-app/pull/1424))
* Move rewards logic to interal api ([#1509](https://github.com/lbryio/lbry-app/pull/1509))
### Fixed ### Fixed
* Fix content-type not shown correctly in file description ([#863](https://github.com/lbryio/lbry-app/pull/863)) * Fix content-type not shown correctly in file description ([#863](https://github.com/lbryio/lbry-app/pull/863))

View file

@ -1,26 +1,17 @@
import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import InviteNew from './view';
import { import {
selectUserInvitesRemaining, selectUserInvitesRemaining,
selectUserInviteNewIsPending, selectUserInviteNewIsPending,
selectUserInviteNewErrorMessage, selectUserInviteNewErrorMessage,
} from 'redux/selectors/user'; } from 'redux/selectors/user';
import rewards from 'rewards';
import { makeSelectRewardAmountByType } from 'redux/selectors/rewards';
import { doUserInviteNew } from 'redux/actions/user'; import { doUserInviteNew } from 'redux/actions/user';
import InviteNew from './view';
const select = state => { const select = state => ({
const selectReward = makeSelectRewardAmountByType();
return {
errorMessage: selectUserInviteNewErrorMessage(state), errorMessage: selectUserInviteNewErrorMessage(state),
invitesRemaining: selectUserInvitesRemaining(state), invitesRemaining: selectUserInvitesRemaining(state),
isPending: selectUserInviteNewIsPending(state), isPending: selectUserInviteNewIsPending(state),
rewardAmount: selectReward(state, { reward_type: rewards.TYPE_REFERRAL }), });
};
};
const perform = dispatch => ({ const perform = dispatch => ({
inviteNew: email => dispatch(doUserInviteNew(email)), inviteNew: email => dispatch(doUserInviteNew(email)),

View file

@ -29,8 +29,7 @@ class FormInviteNew extends React.PureComponent {
} }
render() { render() {
const { errorMessage, isPending, rewardAmount } = this.props; const { errorMessage, isPending } = this.props;
const label = `${__('Get')} ${rewardAmount} LBC`;
return ( return (
<Form onSubmit={this.handleSubmit}> <Form onSubmit={this.handleSubmit}>
@ -49,7 +48,7 @@ class FormInviteNew extends React.PureComponent {
/> />
</FormRow> </FormRow>
<div className="card__actions"> <div className="card__actions">
<Submit label={label} disabled={isPending} /> <Submit label="Invite" disabled={isPending} />
</div> </div>
</Form> </Form>
); );

View file

@ -16,7 +16,7 @@ const makeSelect = () => {
const select = (state, props) => ({ const select = (state, props) => ({
errorMessage: selectError(state, props), errorMessage: selectError(state, props),
isPending: selectIsPending(state, props), isPending: selectIsPending(state, props),
reward: selectReward(state, props), reward: selectReward(state, props.reward_type),
}); });
return select; return select;

View file

@ -1,4 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doNotify, MODALS } from 'lbry-redux';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import { doUserIdentityVerify } from 'redux/actions/user'; import { doUserIdentityVerify } from 'redux/actions/user';
import rewards from 'rewards'; import rewards from 'rewards';
@ -8,15 +9,14 @@ import {
selectIdentityVerifyErrorMessage, selectIdentityVerifyErrorMessage,
} from 'redux/selectors/user'; } from 'redux/selectors/user';
import UserVerify from './view'; import UserVerify from './view';
import { doNotify, MODALS } from 'lbry-redux';
const select = (state, props) => { const select = state => {
const selectReward = makeSelectRewardByType(); const selectReward = makeSelectRewardByType();
return { return {
isPending: selectIdentityVerifyIsPending(state), isPending: selectIdentityVerifyIsPending(state),
errorMessage: selectIdentityVerifyErrorMessage(state), errorMessage: selectIdentityVerifyErrorMessage(state),
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), reward: selectReward(state, rewards.TYPE_NEW_USER),
}; };
}; };

View file

@ -8,7 +8,7 @@ const select = state => {
const selectReward = makeSelectRewardByType(); const selectReward = makeSelectRewardByType();
return { return {
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), reward: selectReward(state, rewards.TYPE_NEW_USER),
}; };
}; };

View file

@ -6,7 +6,7 @@ import CreditAmount from 'component/common/credit-amount';
class ModalFirstReward extends React.PureComponent { class ModalFirstReward extends React.PureComponent {
render() { render() {
const { closeModal, reward } = this.props; const { closeModal } = this.props;
return ( return (
<Modal <Modal
@ -18,10 +18,7 @@ class ModalFirstReward extends React.PureComponent {
> >
<section> <section>
<h3 className="modal__header">{__('Your First Reward')}</h3> <h3 className="modal__header">{__('Your First Reward')}</h3>
<p> <p>{__('You just earned your first reward!')}</p>
{__('You just earned your first reward of')}{' '}
<CreditAmount amount={reward.reward_amount} />.
</p>
<p> <p>
{__( {__(
"This reward will show in your Wallet in the top right momentarily (if it hasn't already)." "This reward will show in your Wallet in the top right momentarily (if it hasn't already)."

View file

@ -1,7 +1,7 @@
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import Lbryio from 'lbryio'; import Lbryio from 'lbryio';
import { doNotify, MODALS } from 'lbry-redux'; import { doNotify, MODALS } from 'lbry-redux';
import { selectUnclaimedRewardsByType } from 'redux/selectors/rewards'; import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { selectUserIsRewardApproved } from 'redux/selectors/user'; import { selectUserIsRewardApproved } from 'redux/selectors/user';
import rewards from 'rewards'; import rewards from 'rewards';
@ -30,8 +30,8 @@ export function doRewardList() {
export function doClaimRewardType(rewardType) { export function doClaimRewardType(rewardType) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const rewardsByType = selectUnclaimedRewardsByType(state); const unclaimedRewards = selectUnclaimedRewards(state);
const reward = rewardsByType[rewardType]; const reward = unclaimedRewards.find(ur => ur.reward_type === rewardType);
const userIsRewardApproved = selectUserIsRewardApproved(state); const userIsRewardApproved = selectUserIsRewardApproved(state);
if (!reward || reward.transaction_id) { if (!reward || reward.transaction_id) {
@ -84,14 +84,14 @@ export function doClaimRewardType(rewardType) {
export function doClaimEligiblePurchaseRewards() { export function doClaimEligiblePurchaseRewards() {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const rewardsByType = selectUnclaimedRewardsByType(state); const unclaimedRewards = selectUnclaimedRewards(state);
const userIsRewardApproved = selectUserIsRewardApproved(state); const userIsRewardApproved = selectUserIsRewardApproved(state);
if (!userIsRewardApproved || !Lbryio.enabled) { if (!userIsRewardApproved || !Lbryio.enabled) {
return; return;
} }
if (rewardsByType[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_FEATURED_DOWNLOAD].forEach(type => { [rewards.TYPE_MANY_DOWNLOADS, rewards.TYPE_FEATURED_DOWNLOAD].forEach(type => {

View file

@ -4,7 +4,7 @@ const reducers = {};
const defaultState = { const defaultState = {
fetching: false, fetching: false,
claimedRewardsById: {}, // id => reward claimedRewardsById: {}, // id => reward
unclaimedRewardsByType: {}, unclaimedRewards: [],
claimPendingByType: {}, claimPendingByType: {},
claimErrorsByType: {}, claimErrorsByType: {},
}; };
@ -17,19 +17,19 @@ reducers[ACTIONS.FETCH_REWARDS_STARTED] = state =>
reducers[ACTIONS.FETCH_REWARDS_COMPLETED] = (state, action) => { reducers[ACTIONS.FETCH_REWARDS_COMPLETED] = (state, action) => {
const { userRewards } = action.data; const { userRewards } = action.data;
const unclaimedRewards = {}; const unclaimedRewards = [];
const claimedRewards = {}; const claimedRewards = {};
userRewards.forEach(reward => { userRewards.forEach(reward => {
if (reward.transaction_id) { if (reward.transaction_id) {
claimedRewards[reward.id] = reward; claimedRewards[reward.id] = reward;
} else { } else {
unclaimedRewards[reward.reward_type] = reward; unclaimedRewards.push(reward);
} }
}); });
return Object.assign({}, state, { return Object.assign({}, state, {
claimedRewardsById: claimedRewards, claimedRewardsById: claimedRewards,
unclaimedRewardsByType: unclaimedRewards, unclaimedRewards,
fetching: false, fetching: false,
}); });
}; };
@ -62,24 +62,21 @@ reducers[ACTIONS.CLAIM_REWARD_STARTED] = (state, action) => {
reducers[ACTIONS.CLAIM_REWARD_SUCCESS] = (state, action) => { reducers[ACTIONS.CLAIM_REWARD_SUCCESS] = (state, action) => {
const { reward } = action.data; const { reward } = action.data;
const { unclaimedRewards } = state;
const unclaimedRewardsByType = Object.assign({}, state.unclaimedRewardsByType); const index = unclaimedRewards.findIndex(ur => ur.reward_type === reward.reward_type);
const existingReward = unclaimedRewardsByType[reward.reward_type]; unclaimedRewards.splice(index, 1);
const newReward = Object.assign({}, reward, { const { claimedRewardsById } = state;
reward_title: existingReward.reward_title, claimedRewardsById[reward.id] = reward;
reward_description: existingReward.reward_description,
});
const claimedRewardsById = Object.assign({}, state.claimedRewardsById); const newState = {
claimedRewardsById[reward.id] = newReward; ...state,
unclaimedRewards: [...unclaimedRewards],
claimedRewardsById: { ...claimedRewardsById },
};
const newState = Object.assign({}, state, { return setClaimRewardState(newState, reward, false, '');
unclaimedRewardsByType,
claimedRewardsById,
});
return setClaimRewardState(newState, newReward, false, '');
}; };
reducers[ACTIONS.CLAIM_REWARD_FAILURE] = (state, action) => { reducers[ACTIONS.CLAIM_REWARD_FAILURE] = (state, action) => {

View file

@ -1,5 +1,4 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import REWARDS from 'rewards';
const selectState = state => state.rewards || {}; const selectState = state => state.rewards || {};
@ -26,16 +25,7 @@ export const selectClaimedRewardsByTransactionId = createSelector(selectClaimedR
}, {}) }, {})
); );
export const selectUnclaimedRewards = createSelector( export const selectUnclaimedRewards = createSelector(selectState, state => state.unclaimedRewards);
selectUnclaimedRewardsByType,
byType =>
Object.values(byType).sort(
(a, b) =>
REWARDS.SORT_ORDER.indexOf(a.reward_type) < REWARDS.SORT_ORDER.indexOf(b.reward_type)
? -1
: 1
) || []
);
export const selectFetchingRewards = createSelector(selectState, state => !!state.fetching); export const selectFetchingRewards = createSelector(selectState, state => !!state.fetching);
@ -65,7 +55,8 @@ const selectClaimRewardError = (state, props) =>
export const makeSelectClaimRewardError = () => export const makeSelectClaimRewardError = () =>
createSelector(selectClaimRewardError, errorMessage => errorMessage); createSelector(selectClaimRewardError, errorMessage => errorMessage);
const selectRewardByType = (state, props) => selectUnclaimedRewardsByType(state)[props.reward_type]; const selectRewardByType = (state, rewardType) =>
selectUnclaimedRewards(state).find(reward => reward.reward_type === rewardType);
export const makeSelectRewardByType = () => createSelector(selectRewardByType, reward => reward); export const makeSelectRewardByType = () => createSelector(selectRewardByType, reward => reward);

View file

@ -1,21 +1,6 @@
import { Lbry, doNotify } from 'lbry-redux'; import { Lbry, doNotify } from 'lbry-redux';
import Lbryio from 'lbryio'; import Lbryio from 'lbryio';
function rewardMessage(type, amount) {
return {
new_developer: __('You earned %s for registering as a new developer.', amount),
new_user: __('You earned %s LBC new user reward.', amount),
verified_email: __('You earned %s LBC for verifying your email address.', amount),
new_channel: __('You earned %s LBC for creating a publisher identity.', amount),
first_stream: __('You earned %s LBC for streaming your first video.', amount),
many_downloads: __('You earned %s LBC for downloading a bunch of things.', amount),
first_publish: __('You earned %s LBC for making your first publication.', amount),
featured_download: __('You earned %s LBC for watching a featured download.', amount),
referral: __('You earned %s LBC for referring someone.', amount),
youtube_creator: __('You earned %s LBC for syncing your YouTube channel.', amount),
}[type];
}
const rewards = {}; const rewards = {};
rewards.TYPE_NEW_DEVELOPER = 'new_developer'; rewards.TYPE_NEW_DEVELOPER = 'new_developer';
@ -28,18 +13,6 @@ rewards.TYPE_FIRST_PUBLISH = 'first_publish';
rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download'; rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download';
rewards.TYPE_REFERRAL = 'referral'; rewards.TYPE_REFERRAL = 'referral';
rewards.YOUTUBE_CREATOR = 'youtube_creator'; rewards.YOUTUBE_CREATOR = 'youtube_creator';
rewards.SORT_ORDER = [
rewards.TYPE_NEW_USER,
rewards.TYPE_CONFIRM_EMAIL,
rewards.TYPE_FIRST_STREAM,
rewards.TYPE_FIRST_CHANNEL,
rewards.TYPE_FIRST_PUBLISH,
rewards.TYPE_FEATURED_DOWNLOAD,
rewards.TYPE_MANY_DOWNLOADS,
rewards.TYPE_REFERRAL,
rewards.TYPE_NEW_DEVELOPER,
rewards.YOUTUBE_CREATOR,
];
rewards.claimReward = type => { rewards.claimReward = type => {
function requestReward(resolve, reject, params) { function requestReward(resolve, reject, params) {
@ -48,7 +21,8 @@ rewards.claimReward = type => {
return; return;
} }
Lbryio.call('reward', 'new', params, 'post').then(reward => { Lbryio.call('reward', 'new', params, 'post').then(reward => {
const message = rewardMessage(type, reward.reward_amount); const message =
reward.reward_notification || `You have claimed a ${reward.reward_amount} LBC reward.`;
// Display global notice // Display global notice
const action = doNotify({ const action = doNotify({