diff --git a/ui/js/actions/rewards.js b/ui/js/actions/rewards.js index 4c12d9c20..277be8a80 100644 --- a/ui/js/actions/rewards.js +++ b/ui/js/actions/rewards.js @@ -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) + } + } } diff --git a/ui/js/component/auth/index.js b/ui/js/component/auth/index.js new file mode 100644 index 000000000..5cddc68f6 --- /dev/null +++ b/ui/js/component/auth/index.js @@ -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) diff --git a/ui/js/component/auth.js b/ui/js/component/auth/view.jsx similarity index 96% rename from ui/js/component/auth.js rename to ui/js/component/auth/view.jsx index fa4bef0d1..c8e29787a 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth/view.jsx @@ -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 ? { - 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 diff --git a/ui/js/component/rewardLink/index.js b/ui/js/component/rewardLink/index.js new file mode 100644 index 000000000..736fa3b30 --- /dev/null +++ b/ui/js/component/rewardLink/index.js @@ -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) diff --git a/ui/js/component/rewardLink/view.jsx b/ui/js/component/rewardLink/view.jsx new file mode 100644 index 000000000..003f1758e --- /dev/null +++ b/ui/js/component/rewardLink/view.jsx @@ -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 ( +//
+// {this.props.claimed +// ? Reward claimed. +// : { this.claimReward() }} />} +// {this.state.errorMessage ? +// { this.clearError() }}> +// {this.state.errorMessage} +// +// : ''} +//
+// ); +// } +// } + + +const RewardLink = (props) => { + const { + reward, + claimed, + button, + pending, + claimable = true, + claimReward, + errorMessage, + clearError, + } = props + + return ( +
+ {claimed + ? Reward claimed. + : { claimReward(reward) }} />} + {errorMessage ? + { clearError() }}> + {errorMessage} + + : ''} +
+ ) +} +export default RewardLink \ No newline at end of file diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 42485e7aa..460893f89 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -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' + diff --git a/ui/js/main.js b/ui/js/main.js index 1a5d79398..178979e6b 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -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'; diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js new file mode 100644 index 000000000..2ef1e9363 --- /dev/null +++ b/ui/js/page/rewards/index.js @@ -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) diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx new file mode 100644 index 000000000..3f41b8fb5 --- /dev/null +++ b/ui/js/page/rewards/view.jsx @@ -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 ( +
+
+
+ +

{reward.title}

+
+
+ {claimed + ? Reward claimed. + : } +
+
{reward.reward_description}
+
+
+ ) +} + +const RewardsPage = (props) => { + const { + fetching, + rewards, + } = props + + let content + + if (fetching) content =
Fetching rewards
+ if (!fetching && rewards.length == 0) content =
Failed to load rewards.
+ if (!fetching && rewards.length > 0) { + content = rewards.map(reward => ) + } + + return ( +
+ + {content} +
+ ) +} + +export default RewardsPage; diff --git a/ui/js/reducers/rewards.js b/ui/js/reducers/rewards.js index c9c3efc21..dcdaa7210 100644 --- a/ui/js/reducers/rewards.js +++ b/ui/js/reducers/rewards.js @@ -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); diff --git a/ui/js/selectors/rewards.js b/ui/js/selectors/rewards.js index 481b1f184..9d62ba7c8 100644 --- a/ui/js/selectors/rewards.js +++ b/ui/js/selectors/rewards.js @@ -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 + ) +}