good chunk of progress towards auth and rewards refactor / degating

This commit is contained in:
Jeremy Kauffman 2017-06-01 20:51:52 -04:00
parent eb170b9720
commit a17d19038e
18 changed files with 344 additions and 196 deletions

View file

@ -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 }
})
}
}

View file

@ -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,
})
}
}

View file

@ -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)

View file

@ -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 (
<section>
<form onSubmit={(event) => { this.handleSubmit(event) }}>
<FormRow ref={(ref) => { this._emailRow = ref }} type="text" label="Email" placeholder="scrwvwls@lbry.io"
name="email" value={this.state.email}
onChange={(event) => { this.handleEmailChanged(event) }} />
<div className="form-row-submit form-row-submit--with-footer">
<Link button="primary" label="Next" disabled={this.state.submitting} onClick={(event) => { this.handleSubmit(event) }} />
</div>
<UserEmailNew />
<div className="form-row-submit">
{ this.state.showNoEmailConfirm ?
<div>
<p className="help form-input-width">If you continue without an email, you will be ineligible to earn free LBC rewards, as well as unable to receive security related communications.</p>
@ -80,7 +41,7 @@ class SubmitEmailStage extends React.Component {
:
<Link className="button-text-help" onClick={ () => { this.onEmailSkipClick() }} label="Do I have to?" />
}
</form>
</div>
</section>
);
}
@ -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 = <PendingStage />;
} else if (!user.has_email) {
stageContent = <EmailStage userEmailDecline={userEmailDecline} />;
}
else {
return null
//StageContent = this._stages[this.state.stage];
}
if (!StageContent) {
return <span className="empty">Unknown authentication step.</span>
}
return <ModalPage className="modal-page--full" isOpen={true} contentLabel="Authentication">
<h1>LBRY Early Access</h1>
{stageContent}
</ModalPage>;
//setStage={(stage, stageProps) => { this.setStage(stage, stageProps) }} {...this.state.stageProps}
return (
true || this.state.stage != "welcome" ?

View file

@ -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 (
<div className="form-row">

View file

@ -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)

View file

@ -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 (
// <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,
errorMessage,
isClaimed,
isPending
} = props
console.log(props)
return (
<div className="reward-link">
{claimed
{isClaimed
? <span><Icon icon="icon-check" /> Reward claimed.</span>
: <Link button={button ? button : 'alt'} disabled={pending || !claimable }
label={ pending ? "Claiming..." : "Claim Reward"} onClick={() => { claimReward(reward) }} />}
: <Link button={button ? button : 'alt'} disabled={isPending}
label={ isPending ? "Claiming..." : "Claim Reward"} onClick={() => { claimReward(reward) }} />}
{errorMessage ?
<Modal isOpen={true} contentLabel="Reward Claim Error" className="error-modal" onConfirmed={() => { clearError() }}>
<Modal isOpen={true} contentLabel="Reward Claim Error" className="error-modal" onConfirmed={() => { clearError(reward) }}>
{errorMessage}
</Modal>
: ''}

View file

@ -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)

View file

@ -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 <form onSubmit={(event) => { this.handleSubmit(event) }}>
<FormRow type="text" label="Email" placeholder="scrwvwls@lbry.io"
name="email" value={this.state.email}
errorMessage={errorMessage}
onChange={(event) => { this.handleEmailChanged(event) }} />
<div className="form-row-submit">
<Link button="primary" label="Next" disabled={isPending} onClick={(event) => { this.handleSubmit(event) }} />
</div>
</form>
}
}
export default UserEmailNew

View file

@ -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'

View file

@ -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) => {

View file

@ -4,6 +4,7 @@ import {
} from 'react-redux'
import {
selectFetchingRewards,
selectIsRewardEligible,
selectRewards,
} from 'selectors/rewards'
import RewardsPage from './view'

View file

@ -16,7 +16,7 @@ const RewardTile = (props) => {
<div className="card__inner">
<div className="card__title-primary">
<CreditAmount amount={reward.reward_amount} />
<h3>{reward.title}</h3>
<h3>{reward.reward_title}</h3>
</div>
<div className="card__actions">
{claimed

View file

@ -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) {

View file

@ -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);

View file

@ -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;

View file

@ -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]
}

View file

@ -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
)