diff --git a/ui/js/actions/rewards.js b/ui/js/actions/rewards.js index 277be8a80..922e7976d 100644 --- a/ui/js/actions/rewards.js +++ b/ui/js/actions/rewards.js @@ -26,20 +26,36 @@ export function doClaimReward(reward) { type: types.CLAIM_REWARD_STARTED, data: { reward } }) - try { - 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) + + const success = (a) => { + console.log(a) + dispatch({ + type: types.CLAIM_REWARD_SUCCESS, + data: { + a + } + }) } + + const failure = (error) => { + dispatch({ + type: types.CLAIM_REWARD_FAILURE, + data: { + reward, + error + } + }) + } + + rewards.claimReward(reward.reward_type).then(success, failure) + } +} + +export function doClaimRewardClearError(reward) { + return function(dispatch, getState) { + dispatch({ + type: types.CLAIM_REWARD_CLEAR_ERROR, + data: { reward } + }) } } diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js index d616c3ac4..da8c73c61 100644 --- a/ui/js/actions/user.js +++ b/ui/js/actions/user.js @@ -1,5 +1,11 @@ import * as types from 'constants/action_types' import lbryio from 'lbryio' +import { + setLocal +} from 'utils' +import { + doFetchRewards +} from 'actions/rewards' export function doAuthenticate() { return function(dispatch, getState) { @@ -18,4 +24,39 @@ export function doAuthenticate() { }) }) } +} + +export function doUserEmailNew(email) { + return function(dispatch, getState) { + dispatch({ + type: types.USER_EMAIL_NEW_STARTED, + }) + lbryio.call('user_email', 'new', { email }, 'post').then(() => { + dispatch({ + type: types.USER_EMAIL_NEW_SUCCESS, + data: { email } + }) + }, (error) => { + if (error.xhr && (error.xhr.status == 409 || error.message == "This email is already in use")) { + dispatch({ + type: types.USER_EMAIL_NEW_EXISTS, + data: { email } + }) + } else { + dispatch({ + type: types.USER_EMAIL_NEW_FAILURE, + data: { error: error.message } + }) + } + }); + } +} + +export function doUserEmailDecline() { + return function(dispatch, getState) { + setLocal('user_email_declined', true) + dispatch({ + type: types.USER_EMAIL_DECLINE, + }) + } } \ No newline at end of file diff --git a/ui/js/component/authOverlay/index.jsx b/ui/js/component/authOverlay/index.jsx index 7e08a5ef9..c350bd771 100644 --- a/ui/js/component/authOverlay/index.jsx +++ b/ui/js/component/authOverlay/index.jsx @@ -3,19 +3,23 @@ import { connect } from 'react-redux' import { - doStartUpgrade, - doCancelUpgrade, -} from 'actions/app' + doUserEmailDecline +} from 'actions/user' import { selectAuthenticationIsPending, + selectEmailNewDeclined, + selectUser, } from 'selectors/user' import AuthOverlay from './view' const select = (state) => ({ - isPending: selectAuthenticationIsPending(state) + isPending: selectAuthenticationIsPending(state), + isEmailDeclined: selectEmailNewDeclined(state), + user: selectUser(state), }) const perform = (dispatch) => ({ + userEmailDecline: () => dispatch(doUserEmailDecline()) }) export default connect(select, perform)(AuthOverlay) diff --git a/ui/js/component/authOverlay/view.jsx b/ui/js/component/authOverlay/view.jsx index 20025820a..1356892fe 100644 --- a/ui/js/component/authOverlay/view.jsx +++ b/ui/js/component/authOverlay/view.jsx @@ -5,73 +5,34 @@ import Modal from "component/modal.js"; import ModalPage from "component/modal-page.js"; import Link from "component/link" import {BusyMessage} from "component/common" -import {RewardLink} from 'component/reward-link'; +import {RewardLink} from 'component/rewardLink'; +import UserEmailNew from 'component/userEmailNew'; 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 { +class EmailStage extends React.Component { constructor(props) { super(props); this.state = { - rewardType: null, - email: '', showNoEmailConfirm: false, - submitting: false }; } - handleEmailChanged(event) { - this.setState({ - email: event.target.value, - }); - } - - onEmailSaved(email) { - this.props.setStage("confirm", { email: email }) - } - onEmailSkipClick() { this.setState({ showNoEmailConfirm: true }) } onEmailSkipConfirm() { - setLocal('auth_bypassed', true); - this.props.setStage(null) - } - - handleSubmit(event) { - event.preventDefault(); - - this.setState({ - submitting: true, - }); - lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { - this.onEmailSaved(this.state.email); - }, (error) => { - if (error.xhr && (error.xhr.status == 409 || error.message == "This email is already in use")) { - this.onEmailSaved(this.state.email); - return; - } else if (this._emailRow) { - this._emailRow.showError(error.message) - } - this.setState({ submitting: false }); - }); + this.props.userEmailDecline() } render() { return (
-
{ this.handleSubmit(event) }}> - { this._emailRow = ref }} type="text" label="Email" placeholder="scrwvwls@lbry.io" - name="email" value={this.state.email} - onChange={(event) => { this.handleEmailChanged(event) }} /> -
- { this.handleSubmit(event) }} /> -
+ +
{ this.state.showNoEmailConfirm ?

If you continue without an email, you will be ineligible to earn free LBC rewards, as well as unable to receive security related communications.

@@ -80,7 +41,7 @@ class SubmitEmailStage extends React.Component { : { this.onEmailSkipClick() }} label="Do I have to?" /> } - +
); } @@ -91,7 +52,6 @@ class ConfirmEmailStage extends React.Component { super(props); this.state = { - rewardType: null, code: '', submitting: false, errorMessage: null, @@ -275,10 +235,8 @@ export class AuthOverlay extends React.Component { super(props); this._stages = { - pending: PendingStage, error: ErrorStage, nocode: CodeRequiredStage, - email: SubmitEmailStage, confirm: ConfirmEmailStage, welcome: WelcomeStage } @@ -320,21 +278,35 @@ export class AuthOverlay extends React.Component { } render() { - let StageContent + let stageContent + const { isPending, + isEmailDeclined, + user, + userEmailDecline } = this.props - if (isPending) { - StageContent = PendingStage; - } else { + console.log('auth overlay render') + console.log(user) + + if (isEmailDeclined) { return null - StageContent = this._stages[this.state.stage]; + } else if (isPending) { + stageContent = ; + } else if (!user.has_email) { + stageContent = ; + } + else { + return null + //StageContent = this._stages[this.state.stage]; } - if (!StageContent) { - return Unknown authentication step. - } + return +

LBRY Early Access

+ {stageContent} +
; + //setStage={(stage, stageProps) => { this.setStage(stage, stageProps) }} {...this.state.stageProps} return ( true || this.state.stage != "welcome" ? diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 809f79e7b..a9c1ab6bd 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -179,8 +179,8 @@ export class FormRow extends React.Component { this._fieldRequiredText = __("This field is required"); this.state = { - isError: false, - errorMessage: null, + isError: !!props.errorMessage, + errorMessage: typeof props.errorMessage === "string" ? props.errorMessage : '', }; } @@ -225,6 +225,7 @@ export class FormRow extends React.Component { delete fieldProps.label; } delete fieldProps.helper; + delete fieldProps.errorMessage; return (
diff --git a/ui/js/component/rewardLink/index.js b/ui/js/component/rewardLink/index.js index 736fa3b30..883d5bdde 100644 --- a/ui/js/component/rewardLink/index.js +++ b/ui/js/component/rewardLink/index.js @@ -4,17 +4,24 @@ import { } from 'react-redux' import { makeSelectHasClaimedReward, + makeSelectClaimRewardError, + makeSelectIsRewardClaimPending } from 'selectors/rewards' import { doClaimReward, + doClaimRewardClearError } from 'actions/rewards' import RewardLink from './view' const makeSelect = () => { const selectHasClaimedReward = makeSelectHasClaimedReward() + const selectIsPending = makeSelectIsRewardClaimPending() + const selectError = makeSelectClaimRewardError() const select = (state, props) => ({ - claimed: selectHasClaimedReward(state, props) + isClaimed: selectHasClaimedReward(state, props), + errorMessage: selectError(state, props), + isPending: selectIsPending(state, props) }) return select @@ -22,6 +29,7 @@ const makeSelect = () => { const perform = (dispatch) => ({ claimReward: (reward) => dispatch(doClaimReward(reward)), + clearError: (reward) => dispatch(doClaimRewardClearError(reward)) }) export default connect(makeSelect, perform)(RewardLink) diff --git a/ui/js/component/rewardLink/view.jsx b/ui/js/component/rewardLink/view.jsx index 003f1758e..f6e2d1923 100644 --- a/ui/js/component/rewardLink/view.jsx +++ b/ui/js/component/rewardLink/view.jsx @@ -1,116 +1,27 @@ 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, + errorMessage, + isClaimed, + isPending } = props - + console.log(props) return (
- {claimed + {isClaimed ? Reward claimed. - : { claimReward(reward) }} />} + : { claimReward(reward) }} />} {errorMessage ? - { clearError() }}> + { clearError(reward) }}> {errorMessage} : ''} diff --git a/ui/js/component/userEmailNew/index.jsx b/ui/js/component/userEmailNew/index.jsx new file mode 100644 index 000000000..0b5e93d3e --- /dev/null +++ b/ui/js/component/userEmailNew/index.jsx @@ -0,0 +1,23 @@ +import React from 'react' +import { + connect +} from 'react-redux' +import { + doUserEmailNew +} from 'actions/user' +import { + selectEmailNewIsPending, + selectEmailNewErrorMessage, +} from 'selectors/user' +import UserEmailNew from './view' + +const select = (state) => ({ + isPending: selectEmailNewIsPending(state), + errorMessage: selectEmailNewErrorMessage(state), +}) + +const perform = (dispatch) => ({ + addUserEmail: (email) => dispatch(doUserEmailNew(email)) +}) + +export default connect(select, perform)(UserEmailNew) diff --git a/ui/js/component/userEmailNew/view.jsx b/ui/js/component/userEmailNew/view.jsx new file mode 100644 index 000000000..502ceaa70 --- /dev/null +++ b/ui/js/component/userEmailNew/view.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import Link from 'component/link'; +import {FormRow} from 'component/form.js'; + +class UserEmailNew extends React.Component { + constructor(props) { + super(props); + + this.state = { + email: '' + }; + } + + handleEmailChanged(event) { + this.setState({ + email: event.target.value, + }); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.addUserEmail(this.state.email) + } + + render() { + const { + errorMessage, + isPending + } = this.props + + return
{ this.handleSubmit(event) }}> + { this.handleEmailChanged(event) }} /> +
+ { this.handleSubmit(event) }} /> +
+ + } +} + +export default UserEmailNew \ No newline at end of file diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 460893f89..8bcabc328 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -74,10 +74,16 @@ 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' +export const USER_EMAIL_DECLINE = 'USER_EMAIL_DECLINE' +export const USER_EMAIL_NEW_STARTED = 'USER_EMAIL_NEW_STARTED' +export const USER_EMAIL_NEW_SUCCESS = 'USER_EMAIL_NEW_SUCCESS' +export const USER_EMAIL_NEW_EXISTS = 'USER_EMAIL_NEW_EXISTS' +export const USER_EMAIL_NEW_FAILURE = 'USER_EMAIL_NEW_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' - +export const CLAIM_REWARD_SUCCESS = 'CLAIM_REWARD_SUCCESS' +export const CLAIM_REWARD_FAILURE = 'CLAIM_REWARD_FAILURE' +export const CLAIM_REWARD_CLEAR_ERROR = 'CLAIM_REWARD_CLEAR_ERROR' diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 19b3c9616..4fa78b94a 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -144,7 +144,7 @@ lbryio.authenticate = function() { lbryio._authenticationPromise = new Promise((resolve, reject) => { lbry.status().then((response) => { - let installation_id = response.installation_id.substring(0, response.installation_id.length - 6) + "C"; + let installation_id = response.installation_id.substring(0, response.installation_id.length - 2) + "D"; function setCurrentUser() { lbryio.call('user', 'me').then((data) => { diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 2ef1e9363..5e7e0aa5c 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -4,6 +4,7 @@ import { } from 'react-redux' import { selectFetchingRewards, + selectIsRewardEligible, selectRewards, } from 'selectors/rewards' import RewardsPage from './view' diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 3f41b8fb5..1071c4aab 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -16,7 +16,7 @@ const RewardTile = (props) => {
-

{reward.title}

+

{reward.reward_title}

{claimed diff --git a/ui/js/reducers/rewards.js b/ui/js/reducers/rewards.js index dcdaa7210..51d2386a1 100644 --- a/ui/js/reducers/rewards.js +++ b/ui/js/reducers/rewards.js @@ -1,7 +1,11 @@ import * as types from "constants/action_types"; -const reducers = {}; -const defaultState = {}; +const reducers = {} +const defaultState = { + fetching: false, + claimPendingByType: {}, + claimErrorsByType: {} +}; reducers[types.FETCH_REWARDS_STARTED] = function(state, action) { const newRewards = Object.assign({}, state.rewards, { @@ -26,28 +30,49 @@ reducers[types.FETCH_REWARDS_COMPLETED] = function(state, action) { return Object.assign({}, state, newRewards) } +function setClaimRewardState(state, reward, isClaiming, errorMessage="") { + const newClaimPendingByType = Object.assign({}, state.claimPendingByType) + const newClaimErrorsByType = Object.assign({}, state.claimErrorsByType) + newClaimPendingByType[reward.reward_type] = isClaiming + newClaimErrorsByType[reward.reward_type] = errorMessage + + return Object.assign({}, state, { + claimPendingByType: newClaimPendingByType, + claimErrorsByType: newClaimErrorsByType, + }) +} + reducers[types.CLAIM_REWARD_STARTED] = function(state, action) { const { reward, } = action.data - const newRewards = Object.assign({}, state, { - claiming: true, - }) - - return Object.assign({}, state, newRewards) + return setClaimRewardState(state, reward, true, "") } -reducers[types.CLAIM_REWARD_COMPLETED] = function(state, action) { +reducers[types.CLAIM_REWARD_SUCCESS] = function(state, action) { const { reward, } = action.data - const newRewards = Object.assign({}, state, { - claiming: false, - }) + return setClaimRewardState(state, reward, false, "") +} - return Object.assign({}, state, newRewards) +reducers[types.CLAIM_REWARD_FAILURE] = function(state, action) { + const { + reward, + error + } = action.data + + return setClaimRewardState(state, reward, false, error.message) +} + +reducers[types.CLAIM_REWARD_CLEAR_ERROR] = function(state, action) { + const { + reward + } = action.data + + return setClaimRewardState(state, reward, state.claimPendingByType[reward.reward_type], "") } export default function reducer(state = defaultState, action) { diff --git a/ui/js/reducers/user.js b/ui/js/reducers/user.js index f5475dfcc..40b2c1627 100644 --- a/ui/js/reducers/user.js +++ b/ui/js/reducers/user.js @@ -1,9 +1,15 @@ import * as types from 'constants/action_types' +import { + getLocal +} from 'utils' const reducers = {} const defaultState = { authenticationIsPending: false, + emailNewIsPending: false, + emailNewErrorMessage: '', + emailNewDeclined: getLocal('user_email_declined', false), user: undefined } @@ -27,6 +33,40 @@ reducers[types.AUTHENTICATION_FAILURE] = function(state, action) { }) } +reducers[types.USER_EMAIL_DECLINE] = function(state, action) { + return Object.assign({}, state, { + emailNewDeclined: true + }) +} + +reducers[types.USER_EMAIL_NEW_STARTED] = function(state, action) { + return Object.assign({}, state, { + emailNewIsPending: true, + emailNewErrorMessage: '' + }) +} + +reducers[types.USER_EMAIL_NEW_SUCCESS] = function(state, action) { + return Object.assign({}, state, { + emailNewIsPending: false, + }) +} + +reducers[types.USER_EMAIL_NEW_EXISTS] = function(state, action) { + return Object.assign({}, state, { + emailNewIsPending: false, + }) +} + +reducers[types.USER_EMAIL_NEW_FAILURE] = function(state, action) { + return Object.assign({}, state, { + emailNewIsPending: false, + emailNewErrorMessage: action.data.error + }) +} + + + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 25f6dbe82..33d5547bc 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -93,7 +93,7 @@ rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download'; rewards.claimReward = function(type) { function requestReward(resolve, reject, params) { - if (!lbryio.enabled) { + if (!lbryio.enabled || !lbryio.getAccessToken()) { reject(new Error(__('Rewards are not enabled.'))); return; } @@ -193,7 +193,10 @@ rewards.claimReward = function(type) { }); }; -rewards.claimEligiblePurchaseRewards = function() { +rewards.claimEligiblePurchaseRewards = () => { + if (!lbryio.enabled || !lbryio.getAccessToken()) { + return; + } let types = {}; types[rewards.TYPE_FIRST_STREAM] = false; types[rewards.TYPE_FEATURED_DOWNLOAD] = false; diff --git a/ui/js/selectors/rewards.js b/ui/js/selectors/rewards.js index 9d62ba7c8..3b4af4e83 100644 --- a/ui/js/selectors/rewards.js +++ b/ui/js/selectors/rewards.js @@ -1,4 +1,5 @@ import { createSelector } from "reselect"; +import { selectUser } from "selectors/user"; const _selectState = state => state.rewards || {}; @@ -12,6 +13,11 @@ export const selectRewards = createSelector( (byType) => Object.values(byType) || [] ) +export const selectIsRewardEligible = createSelector( + selectUser, + (user) => user.can_claim_rewards +) + export const selectClaimedRewards = createSelector( selectRewards, (rewards) => rewards.filter(reward => reward.transaction_id !== "") @@ -20,7 +26,7 @@ export const selectClaimedRewards = createSelector( export const selectClaimedRewardsByType = createSelector( selectClaimedRewards, (claimedRewards) => { - const byType = [] + const byType = {} claimedRewards.forEach(reward => byType[reward.reward_type] = reward) return byType } @@ -42,6 +48,39 @@ export const makeSelectHasClaimedReward = () => { ) } +export const selectClaimsPendingByType = createSelector( + _selectState, + (state) => state.claimPendingByType +) + +const selectIsClaimRewardPending = (state, props) => { + return selectClaimsPendingByType(state, props)[props.reward.reward_type] +} + +export const makeSelectIsRewardClaimPending = () => { + return createSelector( + selectIsClaimRewardPending, + (isClaiming) => isClaiming + ) +} + +export const selectClaimErrorsByType = createSelector( + _selectState, + (state) => state.claimErrorsByType +) + +const selectClaimRewardError = (state, props) => { + console.log(selectClaimErrorsByType(state, props)); + return selectClaimErrorsByType(state, props)[props.reward.reward_type] +} + +export const makeSelectClaimRewardError = () => { + return createSelector( + selectClaimRewardError, + (errorMessage) => errorMessage + ) +} + const selectRewardByType = (state, props) => { return selectRewardsByType(state)[props.reward_type] } diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index 77871ab78..5374fa3fe 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -7,7 +7,22 @@ export const selectAuthenticationIsPending = createSelector( (state) => state.authenticationIsPending ) -export const selectAuthenticationIsFailed = createSelector( +export const selectUser = createSelector( _selectState, - (state) => state.user === null + (state) => state.user +) + +export const selectEmailNewIsPending = createSelector( + _selectState, + (state) => state.emailNewIsPending +) + +export const selectEmailNewErrorMessage = createSelector( + _selectState, + (state) => state.emailNewErrorMessage +) + +export const selectEmailNewDeclined = createSelector( + _selectState, + (state) => state.emailNewDeclined ) \ No newline at end of file