Working on rewards refactor
This commit is contained in:
parent
e0b8afe028
commit
eb170b9720
11 changed files with 394 additions and 27 deletions
|
@ -20,16 +20,26 @@ export function doFetchRewards() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doClaimReward(rewardType) {
|
export function doClaimReward(reward) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.CLAIM_REWARD_STARTED,
|
||||||
|
data: { reward }
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
rewards.claimReward(rewards[rewardType]);
|
const success = (a) => {
|
||||||
dispatch({
|
console.log(a)
|
||||||
type: types.REWARD_CLAIMED,
|
dispatch({
|
||||||
data: {
|
type: types.CLAIM_REWARD_COMPLETED,
|
||||||
reward: rewards[rewardType],
|
data: {
|
||||||
},
|
a
|
||||||
});
|
}
|
||||||
} catch (err) {}
|
})
|
||||||
};
|
}
|
||||||
|
const failure = (a) => console.error(a)
|
||||||
|
rewards.claimReward(reward.reward_type).then(success, failure)
|
||||||
|
} catch(err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
27
ui/js/component/auth/index.js
Normal file
27
ui/js/component/auth/index.js
Normal 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)
|
|
@ -8,7 +8,6 @@ import { RewardLink } from "component/reward-link";
|
||||||
import { FormRow } from "../component/form.js";
|
import { FormRow } from "../component/form.js";
|
||||||
import { CreditAmount, Address } from "../component/common.js";
|
import { CreditAmount, Address } from "../component/common.js";
|
||||||
import { getLocal, setLocal } from "../utils.js";
|
import { getLocal, setLocal } from "../utils.js";
|
||||||
import rewards from "../rewards";
|
|
||||||
|
|
||||||
class SubmitEmailStage extends React.Component {
|
class SubmitEmailStage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -197,7 +196,7 @@ class WelcomeStage extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
hasReward: false,
|
hasReward: true,
|
||||||
rewardAmount: null,
|
rewardAmount: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -210,7 +209,18 @@ class WelcomeStage extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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
|
? <Modal
|
||||||
type="custom"
|
type="custom"
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
|
@ -451,18 +461,11 @@ export class AuthOverlay extends React.Component {
|
||||||
this.setStage("email", {});
|
this.setStage("email", {});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lbryio.call("reward", "list", {}).then(userRewards => {
|
const {
|
||||||
userRewards.filter(function(reward) {
|
claimedRewardsByType,
|
||||||
return (
|
} = this.props
|
||||||
reward.reward_type == rewards.TYPE_NEW_USER &&
|
claimedRewardsByType[rewards.TYPE_NEW_USER] ? this.setStage(null) : this.setStage("welcome")
|
||||||
reward.transaction_id
|
}})
|
||||||
);
|
|
||||||
}).length
|
|
||||||
? this.setStage(null)
|
|
||||||
: this.setStage("welcome");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.setStage("error", { errorText: err.message });
|
this.setStage("error", { errorText: err.message });
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
|
@ -510,3 +513,5 @@ export class AuthOverlay extends React.Component {
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AuthOverlay
|
27
ui/js/component/rewardLink/index.js
Normal file
27
ui/js/component/rewardLink/index.js
Normal 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)
|
120
ui/js/component/rewardLink/view.jsx
Normal file
120
ui/js/component/rewardLink/view.jsx
Normal 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
|
|
@ -74,3 +74,10 @@ export const DAEMON_SETTINGS_RECEIVED = "DAEMON_SETTINGS_RECEIVED";
|
||||||
export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED'
|
export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED'
|
||||||
export const AUTHENTICATION_SUCCESS = 'AUTHENTICATION_SUCCESS'
|
export const AUTHENTICATION_SUCCESS = 'AUTHENTICATION_SUCCESS'
|
||||||
export const AUTHENTICATION_FAILURE = 'AUTHENTICATION_FAILURE'
|
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'
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SnackBar from 'component/snackBar';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import store from 'store.js';
|
import store from 'store.js';
|
||||||
import SplashScreen from 'component/splash.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 { doChangePath, doNavigate, doDaemonReady } from 'actions/app';
|
||||||
import { toQueryString } from 'util/query_params';
|
import { toQueryString } from 'util/query_params';
|
||||||
|
|
||||||
|
|
19
ui/js/page/rewards/index.js
Normal file
19
ui/js/page/rewards/index.js
Normal 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)
|
54
ui/js/page/rewards/view.jsx
Normal file
54
ui/js/page/rewards/view.jsx
Normal 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;
|
|
@ -3,6 +3,53 @@ import * as types from "constants/action_types";
|
||||||
const reducers = {};
|
const reducers = {};
|
||||||
const defaultState = {};
|
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) {
|
export default function reducer(state = defaultState, action) {
|
||||||
const handler = reducers[action.type];
|
const handler = reducers[action.type];
|
||||||
if (handler) return handler(state, action);
|
if (handler) return handler(state, action);
|
||||||
|
|
|
@ -1,3 +1,54 @@
|
||||||
import { createSelector } from "reselect";
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue