Working on rewards refactor

This commit is contained in:
6ea86b96 2017-05-26 12:53:32 +04:00 committed by Jeremy Kauffman
parent e0b8afe028
commit eb170b9720
11 changed files with 394 additions and 27 deletions

View file

@ -20,16 +20,26 @@ export function doFetchRewards() {
};
}
export function doClaimReward(rewardType) {
export function doClaimReward(reward) {
return function(dispatch, getState) {
dispatch({
type: types.CLAIM_REWARD_STARTED,
data: { reward }
})
try {
rewards.claimReward(rewards[rewardType]);
dispatch({
type: types.REWARD_CLAIMED,
data: {
reward: rewards[rewardType],
},
});
} catch (err) {}
};
const success = (a) => {
console.log(a)
dispatch({
type: types.CLAIM_REWARD_COMPLETED,
data: {
a
}
})
}
const failure = (a) => console.error(a)
rewards.claimReward(reward.reward_type).then(success, failure)
} catch(err) {
console.error(err)
}
}
}

View file

@ -0,0 +1,27 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
selectFetchingRewards,
selectClaimedRewardsByType,
makeSelectRewardByType,
} from 'selectors/rewards'
import AuthOverlay from './view'
const makeSelect = () => {
const selectRewardByType = makeSelectRewardByType()
const select = (state) => ({
fetchingRewards: selectFetchingRewards(state),
claimedRewardsByType: selectClaimedRewardsByType(state),
newUserReward: selectRewardByType(state, { reward_type: 'new_user' }),
})
return select
}
const perform = (dispatch) => ({
})
export default connect(makeSelect, perform)(AuthOverlay)

View file

@ -8,7 +8,6 @@ import { RewardLink } from "component/reward-link";
import { FormRow } from "../component/form.js";
import { CreditAmount, Address } from "../component/common.js";
import { getLocal, setLocal } from "../utils.js";
import rewards from "../rewards";
class SubmitEmailStage extends React.Component {
constructor(props) {
@ -197,7 +196,7 @@ class WelcomeStage extends React.Component {
super(props);
this.state = {
hasReward: false,
hasReward: true,
rewardAmount: null,
};
}
@ -210,7 +209,18 @@ class WelcomeStage extends React.Component {
}
render() {
return !this.state.hasReward
const {
claimedRewardsByType,
fetchingRewards,
newUserReward,
} = this.props
const hasReward = claimedRewardsByType.length > 0
if (fetchingRewards) return null
if (!newUserReward) return null
return !hasReward
? <Modal
type="custom"
isOpen={true}
@ -451,18 +461,11 @@ export class AuthOverlay extends React.Component {
this.setStage("email", {});
}
} else {
lbryio.call("reward", "list", {}).then(userRewards => {
userRewards.filter(function(reward) {
return (
reward.reward_type == rewards.TYPE_NEW_USER &&
reward.transaction_id
);
}).length
? this.setStage(null)
: this.setStage("welcome");
});
}
})
const {
claimedRewardsByType,
} = this.props
claimedRewardsByType[rewards.TYPE_NEW_USER] ? this.setStage(null) : this.setStage("welcome")
}})
.catch(err => {
this.setStage("error", { errorText: err.message });
document.dispatchEvent(
@ -510,3 +513,5 @@ export class AuthOverlay extends React.Component {
/>;
}
}
export default AuthOverlay

View file

@ -0,0 +1,27 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
makeSelectHasClaimedReward,
} from 'selectors/rewards'
import {
doClaimReward,
} from 'actions/rewards'
import RewardLink from './view'
const makeSelect = () => {
const selectHasClaimedReward = makeSelectHasClaimedReward()
const select = (state, props) => ({
claimed: selectHasClaimedReward(state, props)
})
return select
}
const perform = (dispatch) => ({
claimReward: (reward) => dispatch(doClaimReward(reward)),
})
export default connect(makeSelect, perform)(RewardLink)

View file

@ -0,0 +1,120 @@
import React from 'react';
import lbry from 'lbry'
import {Icon} from 'component/common';
import Modal from 'component/modal';
import rewards from 'rewards';
import Link from 'component/link'
// class RewardLink extends React.Component {
// static propTypes = {
// type: React.PropTypes.string.isRequired,
// claimed: React.PropTypes.bool,
// onRewardClaim: React.PropTypes.func,
// onRewardFailure: React.PropTypes.func
// }
// constructor(props) {
// super(props);
// this.state = {
// claimable: true,
// pending: false,
// errorMessage: null
// };
// }
// refreshClaimable() {
// switch(this.props.type) {
// case 'new_user':
// this.setState({ claimable: true });
// return;
// case 'first_publish':
// lbry.claim_list_mine().then((list) => {
// this.setState({
// claimable: list.length > 0
// })
// });
// return;
// }
// }
// componentWillMount() {
// this.refreshClaimable();
// }
// claimReward() {
// this.setState({
// pending: true
// })
// rewards.claimReward(this.props.type).then((reward) => {
// this.setState({
// pending: false,
// errorMessage: null
// })
// if (this.props.onRewardClaim) {
// this.props.onRewardClaim(reward);
// }
// }).catch((error) => {
// this.setState({
// errorMessage: error.message,
// pending: false
// })
// })
// }
// clearError() {
// if (this.props.onRewardFailure) {
// this.props.onRewardFailure()
// }
// this.setState({
// errorMessage: null
// })
// }
// render() {
// return (
// <div className="reward-link">
// {this.props.claimed
// ? <span><Icon icon="icon-check" /> Reward claimed.</span>
// : <Link button={this.props.button ? this.props.button : 'alt'} disabled={this.state.pending || !this.state.claimable }
// label={ this.state.pending ? "Claiming..." : "Claim Reward"} onClick={() => { this.claimReward() }} />}
// {this.state.errorMessage ?
// <Modal isOpen={true} contentLabel="Reward Claim Error" className="error-modal" onConfirmed={() => { this.clearError() }}>
// {this.state.errorMessage}
// </Modal>
// : ''}
// </div>
// );
// }
// }
const RewardLink = (props) => {
const {
reward,
claimed,
button,
pending,
claimable = true,
claimReward,
errorMessage,
clearError,
} = props
return (
<div className="reward-link">
{claimed
? <span><Icon icon="icon-check" /> Reward claimed.</span>
: <Link button={button ? button : 'alt'} disabled={pending || !claimable }
label={ pending ? "Claiming..." : "Claim Reward"} onClick={() => { claimReward(reward) }} />}
{errorMessage ?
<Modal isOpen={true} contentLabel="Reward Claim Error" className="error-modal" onConfirmed={() => { clearError() }}>
{errorMessage}
</Modal>
: ''}
</div>
)
}
export default RewardLink

View file

@ -74,3 +74,10 @@ export const DAEMON_SETTINGS_RECEIVED = "DAEMON_SETTINGS_RECEIVED";
export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED'
export const AUTHENTICATION_SUCCESS = 'AUTHENTICATION_SUCCESS'
export const AUTHENTICATION_FAILURE = 'AUTHENTICATION_FAILURE'
// Rewards
export const FETCH_REWARDS_STARTED = 'FETCH_REWARDS_STARTED'
export const FETCH_REWARDS_COMPLETED = 'FETCH_REWARDS_COMPLETED'
export const CLAIM_REWARD_STARTED = 'CLAIM_REWARD_STARTED'
export const CLAIM_REWARD_COMPLETED = 'CLAIM_REWARD_COMPLETED'

View file

@ -7,7 +7,7 @@ import SnackBar from 'component/snackBar';
import { Provider } from 'react-redux';
import store from 'store.js';
import SplashScreen from 'component/splash.js';
import { AuthOverlay } from 'component/auth.js';
import AuthOverlay from 'component/authOverlay';
import { doChangePath, doNavigate, doDaemonReady } from 'actions/app';
import { toQueryString } from 'util/query_params';

View file

@ -0,0 +1,19 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
selectFetchingRewards,
selectRewards,
} from 'selectors/rewards'
import RewardsPage from './view'
const select = (state) => ({
fetching: selectFetchingRewards(state),
rewards: selectRewards(state),
})
const perform = (dispatch) => ({
})
export default connect(select, perform)(RewardsPage)

View file

@ -0,0 +1,54 @@
import React from 'react';
import lbryio from 'lbryio';
import {CreditAmount, Icon} from 'component/common';
import SubHeader from 'component/subHeader'
import RewardLink from 'component/rewardLink';
const RewardTile = (props) => {
const {
reward,
} = props
const claimed = !!reward.transaction_id
return (
<section className="card">
<div className="card__inner">
<div className="card__title-primary">
<CreditAmount amount={reward.reward_amount} />
<h3>{reward.title}</h3>
</div>
<div className="card__actions">
{claimed
? <span><Icon icon="icon-check" /> Reward claimed.</span>
: <RewardLink reward={reward} />}
</div>
<div className="card__content">{reward.reward_description}</div>
</div>
</section>
)
}
const RewardsPage = (props) => {
const {
fetching,
rewards,
} = props
let content
if (fetching) content = <div className="empty">Fetching rewards</div>
if (!fetching && rewards.length == 0) content = <div className="empty">Failed to load rewards.</div>
if (!fetching && rewards.length > 0) {
content = rewards.map(reward => <RewardTile key={reward.reward_type} reward={reward} />)
}
return (
<main className="main--single-column">
<SubHeader />
{content}
</main>
)
}
export default RewardsPage;

View file

@ -3,6 +3,53 @@ import * as types from "constants/action_types";
const reducers = {};
const defaultState = {};
reducers[types.FETCH_REWARDS_STARTED] = function(state, action) {
const newRewards = Object.assign({}, state.rewards, {
fetching: true,
})
return Object.assign({}, state, newRewards)
}
reducers[types.FETCH_REWARDS_COMPLETED] = function(state, action) {
const {
userRewards,
} = action.data
const byRewardType = {}
userRewards.forEach(reward => byRewardType[reward.reward_type] = reward)
const newRewards = Object.assign({}, state.rewards, {
byRewardType: byRewardType,
fetching: false
})
return Object.assign({}, state, newRewards)
}
reducers[types.CLAIM_REWARD_STARTED] = function(state, action) {
const {
reward,
} = action.data
const newRewards = Object.assign({}, state, {
claiming: true,
})
return Object.assign({}, state, newRewards)
}
reducers[types.CLAIM_REWARD_COMPLETED] = function(state, action) {
const {
reward,
} = action.data
const newRewards = Object.assign({}, state, {
claiming: false,
})
return Object.assign({}, state, newRewards)
}
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);

View file

@ -1,3 +1,54 @@
import { createSelector } from "reselect";
export const _selectState = state => state.rewards || {};
const _selectState = state => state.rewards || {};
export const selectRewardsByType = createSelector(
_selectState,
(state) => state.byRewardType || {}
)
export const selectRewards = createSelector(
selectRewardsByType,
(byType) => Object.values(byType) || []
)
export const selectClaimedRewards = createSelector(
selectRewards,
(rewards) => rewards.filter(reward => reward.transaction_id !== "")
)
export const selectClaimedRewardsByType = createSelector(
selectClaimedRewards,
(claimedRewards) => {
const byType = []
claimedRewards.forEach(reward => byType[reward.reward_type] = reward)
return byType
}
)
export const selectFetchingRewards = createSelector(
_selectState,
(state) => !!state.fetching
)
export const selectHasClaimedReward = (state, props) => {
return !!selectClaimedRewardsByType[props.reward.reward_type]
}
export const makeSelectHasClaimedReward = () => {
return createSelector(
selectHasClaimedReward,
(claimed) => claimed
)
}
const selectRewardByType = (state, props) => {
return selectRewardsByType(state)[props.reward_type]
}
export const makeSelectRewardByType = () => {
return createSelector(
selectRewardByType,
(reward) => reward
)
}