added rewards and user actions, reducers and selectors

This commit is contained in:
Akinwale Ariwodola 2018-07-18 12:54:33 +01:00
parent 88f32dfb12
commit 217a603ceb
13 changed files with 2538 additions and 60 deletions

1670
dist/bundle.js vendored

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,3 @@
export const GET_AUTH_TOKEN_STARTED = 'GET_AUTH_TOKEN_STARTED';
export const GET_AUTH_TOKEN_COMPLETED = 'GET_AUTH_TOKEN_COMPLETED';
export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE';
export const GENERATE_AUTH_TOKEN_STARTED = 'GENERATE_AUTH_TOKEN_STARTED';
export const GENERATE_AUTH_TOKEN_SUCCESS = 'GENERATE_AUTH_TOKEN_SUCCESS';

View file

@ -1 +1,71 @@
export { Lbryio } from 'lbryio';
import * as LBRYINC_ACTIONS from 'constants/action_types';
import Lbryio from 'lbryio';
// constants
export { LBRYINC_ACTIONS };
// Lbryio
export { Lbryio };
// actions
export { doGenerateAuthToken } from 'redux/actions/auth';
export {
doRewardList,
doClaimRewardType,
doClaimEligiblePurchaseRewards,
doClaimRewardClearError,
} from 'redux/actions/rewards';
export { doFetchInviteStatus, doInstallNew, doAuthenticate, doUserFetch } from 'redux/actions/user';
// reducers
export { authReducer } from 'redux/reducers/auth';
export { rewardsReducer } from 'redux/reducers/rewards';
export { userReducer } from 'redux/reducers/user';
// selectors
export { selectAuthToken } from 'redux/selectors/auth';
export {
makeSelectClaimRewardError,
makeSelectIsRewardClaimPending,
makeSelectRewardAmountByType,
makeSelectRewardByType,
selectUnclaimedRewardsByType,
selectClaimedRewardsById,
selectClaimedRewards,
selectClaimedRewardsByTransactionId,
selectUnclaimedRewards,
selectFetchingRewards,
selectUnclaimedRewardValue,
selectClaimsPendingByType,
selectIsClaimRewardPending,
selectClaimErrorsByType,
selectClaimRewardError,
selectRewardByType,
} from 'redux/selectors/rewards';
export {
selectAuthenticationIsPending,
selectUserIsPending,
selectUser,
selectUserEmail,
selectUserPhone,
selectUserCountryCode,
selectEmailToVerify,
selectPhoneToVerify,
selectUserIsRewardApproved,
selectEmailNewIsPending,
selectEmailNewErrorMessage,
selectPhoneNewErrorMessage,
selectEmailVerifyIsPending,
selectEmailVerifyErrorMessage,
selectPhoneVerifyErrorMessage,
selectIdentityVerifyIsPending,
selectIdentityVerifyErrorMessage,
selectUserIsVerificationCandidate,
selectAccessToken,
selectUserInviteStatusIsPending,
selectUserInvitesRemaining,
selectUserInvitees,
selectUserInviteStatusFailed,
selectUserInviteNewIsPending,
selectUserInviteNewErrorMessage,
} from 'redux/selectors/user';

View file

@ -1,5 +1,5 @@
import { Lbry } from 'lbry-redux';
import { doNewInstallation } from 'redux/actions/auth';
import { doGenerateAuthToken } from 'redux/actions/auth';
import querystring from 'querystring';
const Lbryio = {
@ -71,7 +71,7 @@ Lbryio.getAuthToken = () =>
if (Lbryio.authToken) {
resolve(Lbryio.authToken);
} else {
const store = global.store || (window.app ? window.app.store : null);
const { store } = window;
if (store) {
const state = store.getState();
const token = state.auth ? state.auth.authToken : null;
@ -118,9 +118,9 @@ Lbryio.authenticate = () => {
}
return Lbry.status().then(status => {
const store = global.store || (window.app ? window.app.store : null);
const { store } = window;
if (store) {
store.dispatch(doNewInstallation(status.installation_id));
store.dispatch(doGenerateAuthToken(status.installation_id));
return resolve();
}

View file

@ -1,10 +1,10 @@
import * as ACTIONS from 'constants/action_types';
import Lbryio from 'lbryio';
export function doNewInstallation(installationId) {
export function doGenerateAuthToken(installationId) {
return dispatch => {
dispatch({
type: ACTIONS.GET_AUTH_TOKEN_STARTED,
type: ACTIONS.GENERATE_AUTH_TOKEN_STARTED,
});
Lbryio.call(
@ -20,20 +20,18 @@ export function doNewInstallation(installationId) {
.then(response => {
if (!response.auth_token) {
dispatch({
type: ACTIONS.GET_AUTH_TOKEN_COMPLETED,
data: { authToken: null },
type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE,
});
} else {
dispatch({
type: ACTIONS.GET_AUTH_TOKEN_COMPLETED,
type: ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS,
data: { authToken: response.auth_token },
});
}
})
.catch(() => {
dispatch({
type: ACTIONS.GET_AUTH_TOKEN_COMPLETED,
data: { authToken: null },
type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE,
});
});
};

View file

@ -0,0 +1,114 @@
import * as ACTIONS from 'constants/action_types';
import Lbryio from 'lbryio';
import { doNotify, MODALS } from 'lbry-redux';
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { selectUserIsRewardApproved } from 'redux/selectors/user';
import rewards from 'rewards';
export function doRewardList() {
return dispatch => {
dispatch({
type: ACTIONS.FETCH_REWARDS_STARTED,
});
Lbryio.call('reward', 'list', { multiple_rewards_per_type: true })
.then(userRewards => {
dispatch({
type: ACTIONS.FETCH_REWARDS_COMPLETED,
data: { userRewards },
});
})
.catch(() => {
dispatch({
type: ACTIONS.FETCH_REWARDS_COMPLETED,
data: { userRewards: [] },
});
});
};
}
export function doClaimRewardType(rewardType, options) {
return (dispatch, getState) => {
const state = getState();
const unclaimedRewards = selectUnclaimedRewards(state);
const reward = unclaimedRewards.find(ur => ur.reward_type === rewardType);
const userIsRewardApproved = selectUserIsRewardApproved(state);
if (!reward || reward.transaction_id) {
// already claimed or doesn't exist, do nothing
return;
}
if (!userIsRewardApproved && rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
const action = doNotify({
id: MODALS.REWARD_APPROVAL_REQUIRED,
isError: false,
});
dispatch(action);
return;
}
dispatch({
type: ACTIONS.CLAIM_REWARD_STARTED,
data: { reward },
});
const success = successReward => {
dispatch({
type: ACTIONS.CLAIM_REWARD_SUCCESS,
data: {
reward: successReward,
},
});
if (successReward.reward_type === rewards.TYPE_NEW_USER) {
const action = doNotify({
id: MODALS.FIRST_REWARD,
isError: false,
});
dispatch(action);
}
};
const failure = error => {
dispatch({
type: ACTIONS.CLAIM_REWARD_FAILURE,
data: {
reward,
error: !options || !options.failSilently ? error : undefined,
},
});
};
rewards.claimReward(rewardType).then(success, failure);
};
}
export function doClaimEligiblePurchaseRewards() {
return (dispatch, getState) => {
const state = getState();
const unclaimedRewards = selectUnclaimedRewards(state);
const userIsRewardApproved = selectUserIsRewardApproved(state);
if (!userIsRewardApproved || !Lbryio.enabled) {
return;
}
if (unclaimedRewards.find(ur => ur.reward_type === rewards.TYPE_FIRST_STREAM)) {
dispatch(doClaimRewardType(rewards.TYPE_FIRST_STREAM));
} else {
[rewards.TYPE_MANY_DOWNLOADS, rewards.TYPE_FEATURED_DOWNLOAD].forEach(type => {
dispatch(doClaimRewardType(type, { failSilently: true }));
});
}
};
}
export function doClaimRewardClearError(reward) {
return dispatch => {
dispatch({
type: ACTIONS.CLAIM_REWARD_CLEAR_ERROR,
data: { reward },
});
};
}

93
src/redux/actions/user.js Normal file
View file

@ -0,0 +1,93 @@
import { ACTIONS, MODALS, Lbry, doNotify } from 'lbry-redux';
import { doRewardList } from 'redux/actions/rewards';
import Lbryio from 'lbryio';
export function doFetchInviteStatus() {
return dispatch => {
dispatch({
type: ACTIONS.USER_INVITE_STATUS_FETCH_STARTED,
});
Lbryio.call('user', 'invite_status')
.then(status => {
dispatch({
type: ACTIONS.USER_INVITE_STATUS_FETCH_SUCCESS,
data: {
invitesRemaining: status.invites_remaining ? status.invites_remaining : 0,
invitees: status.invitees,
},
});
})
.catch(error => {
dispatch({
type: ACTIONS.USER_INVITE_STATUS_FETCH_FAILURE,
data: { error },
});
});
};
}
export function doInstallNew(appVersion, deviceId = null) {
const payload = { app_version: appVersion, device_id: deviceId };
Lbry.status().then(status => {
payload.app_id = status.installation_id;
payload.node_id = status.lbry_id;
Lbry.version().then(version => {
payload.daemon_version = version.lbrynet_version;
payload.operating_system = version.os_system;
payload.platform = version.platform;
Lbryio.call('install', 'new', payload);
});
});
}
// TODO: Call doInstallNew separately so we don't have to pass appVersion and deviceId params?
export function doAuthenticate(appVersion, deviceId = null) {
return dispatch => {
dispatch({
type: ACTIONS.AUTHENTICATION_STARTED,
});
Lbryio.authenticate()
.then(user => {
// analytics.setUser(user);
dispatch({
type: ACTIONS.AUTHENTICATION_SUCCESS,
data: { user },
});
dispatch(doRewardList());
dispatch(doFetchInviteStatus());
doInstallNew(appVersion, deviceId);
})
.catch(error => {
dispatch(doNotify({ id: MODALS.AUTHENTICATION_FAILURE }));
dispatch({
type: ACTIONS.AUTHENTICATION_FAILURE,
data: { error },
});
});
};
}
export function doUserFetch() {
return dispatch => {
dispatch({
type: ACTIONS.USER_FETCH_STARTED,
});
Lbryio.getCurrentUser()
.then(user => {
// analytics.setUser(user);
dispatch(doRewardList());
dispatch({
type: ACTIONS.USER_FETCH_SUCCESS,
data: { user },
});
})
.catch(error => {
dispatch({
type: ACTIONS.USER_FETCH_FAILURE,
data: { error },
});
});
};
}

View file

@ -5,12 +5,18 @@ const defaultState = {
authenticating: false,
};
reducers[ACTIONS.GET_AUTH_TOKEN_STARTED] = state =>
reducers[ACTIONS.GENERATE_AUTH_TOKEN_FAILURE] = state =>
Object.assign({}, state, {
authToken: null,
authenticating: false,
});
reducers[ACTIONS.GENERATE_AUTH_TOKEN_STARTED] = state =>
Object.assign({}, state, {
authenticating: true,
});
reducers[ACTIONS.GET_AUTH_TOKEN_COMPLETED] = (state, action) =>
reducers[ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS] = (state, action) =>
Object.assign({}, state, {
authToken: action.authToken,
authenticating: false,

View file

@ -0,0 +1,98 @@
import { ACTIONS } from 'lbry-redux';
const reducers = {};
const defaultState = {
fetching: false,
claimedRewardsById: {}, // id => reward
unclaimedRewards: [],
claimPendingByType: {},
claimErrorsByType: {},
};
reducers[ACTIONS.FETCH_REWARDS_STARTED] = state =>
Object.assign({}, state, {
fetching: true,
});
reducers[ACTIONS.FETCH_REWARDS_COMPLETED] = (state, action) => {
const { userRewards } = action.data;
const unclaimedRewards = [];
const claimedRewards = {};
userRewards.forEach(reward => {
if (reward.transaction_id) {
claimedRewards[reward.id] = reward;
} else {
unclaimedRewards.push(reward);
}
});
return Object.assign({}, state, {
claimedRewardsById: claimedRewards,
unclaimedRewards,
fetching: false,
});
};
function setClaimRewardState(state, reward, isClaiming, errorMessage = '') {
const newClaimPendingByType = Object.assign({}, state.claimPendingByType);
const newClaimErrorsByType = Object.assign({}, state.claimErrorsByType);
if (isClaiming) {
newClaimPendingByType[reward.reward_type] = isClaiming;
} else {
delete newClaimPendingByType[reward.reward_type];
}
if (errorMessage) {
newClaimErrorsByType[reward.reward_type] = errorMessage;
} else {
delete newClaimErrorsByType[reward.reward_type];
}
return Object.assign({}, state, {
claimPendingByType: newClaimPendingByType,
claimErrorsByType: newClaimErrorsByType,
});
}
reducers[ACTIONS.CLAIM_REWARD_STARTED] = (state, action) => {
const { reward } = action.data;
return setClaimRewardState(state, reward, true, '');
};
reducers[ACTIONS.CLAIM_REWARD_SUCCESS] = (state, action) => {
const { reward } = action.data;
const { unclaimedRewards } = state;
const index = unclaimedRewards.findIndex(ur => ur.reward_type === reward.reward_type);
unclaimedRewards.splice(index, 1);
const { claimedRewardsById } = state;
claimedRewardsById[reward.id] = reward;
const newState = {
...state,
unclaimedRewards: [...unclaimedRewards],
claimedRewardsById: { ...claimedRewardsById },
};
return setClaimRewardState(newState, reward, false, '');
};
reducers[ACTIONS.CLAIM_REWARD_FAILURE] = (state, action) => {
const { reward, error } = action.data;
return setClaimRewardState(state, reward, false, error ? error.message : '');
};
reducers[ACTIONS.CLAIM_REWARD_CLEAR_ERROR] = (state, action) => {
const { reward } = action.data;
return setClaimRewardState(state, reward, state.claimPendingByType[reward.reward_type], '');
};
export function rewardsReducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}

222
src/redux/reducers/user.js Normal file
View file

@ -0,0 +1,222 @@
import { ACTIONS } from 'lbry-redux';
const reducers = {};
const defaultState = {
authenticationIsPending: false,
userIsPending: false,
emailNewIsPending: false,
emailNewErrorMessage: '',
emailToVerify: '',
inviteNewErrorMessage: '',
inviteNewIsPending: false,
inviteStatusIsPending: false,
invitesRemaining: undefined,
invitees: undefined,
user: undefined,
};
reducers[ACTIONS.AUTHENTICATION_STARTED] = state =>
Object.assign({}, state, {
authenticationIsPending: true,
userIsPending: true,
user: defaultState.user,
});
reducers[ACTIONS.AUTHENTICATION_SUCCESS] = (state, action) =>
Object.assign({}, state, {
authenticationIsPending: false,
userIsPending: false,
user: action.data.user,
});
reducers[ACTIONS.AUTHENTICATION_FAILURE] = state =>
Object.assign({}, state, {
authenticationIsPending: false,
userIsPending: false,
user: null,
});
reducers[ACTIONS.USER_FETCH_STARTED] = state =>
Object.assign({}, state, {
userIsPending: true,
user: defaultState.user,
});
reducers[ACTIONS.USER_FETCH_SUCCESS] = (state, action) =>
Object.assign({}, state, {
userIsPending: false,
user: action.data.user,
});
reducers[ACTIONS.USER_FETCH_FAILURE] = state =>
Object.assign({}, state, {
userIsPending: true,
user: null,
});
reducers[ACTIONS.USER_PHONE_NEW_STARTED] = (state, action) => {
const user = Object.assign({}, state.user);
user.country_code = action.data.country_code;
return Object.assign({}, state, {
phoneNewIsPending: true,
phoneNewErrorMessage: '',
user,
});
};
reducers[ACTIONS.USER_PHONE_NEW_SUCCESS] = (state, action) =>
Object.assign({}, state, {
phoneToVerify: action.data.phone,
phoneNewIsPending: false,
});
reducers[ACTIONS.USER_PHONE_RESET] = state =>
Object.assign({}, state, {
phoneToVerify: null,
});
reducers[ACTIONS.USER_PHONE_NEW_FAILURE] = (state, action) =>
Object.assign({}, state, {
phoneNewIsPending: false,
phoneNewErrorMessage: action.data.error,
});
reducers[ACTIONS.USER_PHONE_VERIFY_STARTED] = state =>
Object.assign({}, state, {
phoneVerifyIsPending: true,
phoneVerifyErrorMessage: '',
});
reducers[ACTIONS.USER_PHONE_VERIFY_SUCCESS] = (state, action) =>
Object.assign({}, state, {
phoneToVerify: '',
phoneVerifyIsPending: false,
user: action.data.user,
});
reducers[ACTIONS.USER_PHONE_VERIFY_FAILURE] = (state, action) =>
Object.assign({}, state, {
phoneVerifyIsPending: false,
phoneVerifyErrorMessage: action.data.error,
});
reducers[ACTIONS.USER_EMAIL_NEW_STARTED] = state =>
Object.assign({}, state, {
emailNewIsPending: true,
emailNewErrorMessage: '',
});
reducers[ACTIONS.USER_EMAIL_NEW_SUCCESS] = (state, action) => {
const user = Object.assign({}, state.user);
user.primary_email = action.data.email;
return Object.assign({}, state, {
emailToVerify: action.data.email,
emailNewIsPending: false,
user,
});
};
reducers[ACTIONS.USER_EMAIL_NEW_EXISTS] = (state, action) =>
Object.assign({}, state, {
emailToVerify: action.data.email,
emailNewIsPending: false,
});
reducers[ACTIONS.USER_EMAIL_NEW_FAILURE] = (state, action) =>
Object.assign({}, state, {
emailNewIsPending: false,
emailNewErrorMessage: action.data.error,
});
reducers[ACTIONS.USER_EMAIL_VERIFY_STARTED] = state =>
Object.assign({}, state, {
emailVerifyIsPending: true,
emailVerifyErrorMessage: '',
});
reducers[ACTIONS.USER_EMAIL_VERIFY_SUCCESS] = (state, action) => {
const user = Object.assign({}, state.user);
user.primary_email = action.data.email;
return Object.assign({}, state, {
emailToVerify: '',
emailVerifyIsPending: false,
user,
});
};
reducers[ACTIONS.USER_EMAIL_VERIFY_FAILURE] = (state, action) =>
Object.assign({}, state, {
emailVerifyIsPending: false,
emailVerifyErrorMessage: action.data.error,
});
reducers[ACTIONS.USER_IDENTITY_VERIFY_STARTED] = state =>
Object.assign({}, state, {
identityVerifyIsPending: true,
identityVerifyErrorMessage: '',
});
reducers[ACTIONS.USER_IDENTITY_VERIFY_SUCCESS] = (state, action) =>
Object.assign({}, state, {
identityVerifyIsPending: false,
identityVerifyErrorMessage: '',
user: action.data.user,
});
reducers[ACTIONS.USER_IDENTITY_VERIFY_FAILURE] = (state, action) =>
Object.assign({}, state, {
identityVerifyIsPending: false,
identityVerifyErrorMessage: action.data.error,
});
reducers[ACTIONS.FETCH_ACCESS_TOKEN_SUCCESS] = (state, action) => {
const { token } = action.data;
return Object.assign({}, state, {
accessToken: token,
});
};
reducers[ACTIONS.USER_INVITE_STATUS_FETCH_STARTED] = state =>
Object.assign({}, state, {
inviteStatusIsPending: true,
});
reducers[ACTIONS.USER_INVITE_STATUS_FETCH_SUCCESS] = (state, action) =>
Object.assign({}, state, {
inviteStatusIsPending: false,
invitesRemaining: action.data.invitesRemaining,
invitees: action.data.invitees,
});
reducers[ACTIONS.USER_INVITE_NEW_STARTED] = state =>
Object.assign({}, state, {
inviteNewIsPending: true,
inviteNewErrorMessage: '',
});
reducers[ACTIONS.USER_INVITE_NEW_SUCCESS] = state =>
Object.assign({}, state, {
inviteNewIsPending: false,
inviteNewErrorMessage: '',
});
reducers[ACTIONS.USER_INVITE_NEW_FAILURE] = (state, action) =>
Object.assign({}, state, {
inviteNewIsPending: false,
inviteNewErrorMessage: action.data.error.message,
});
reducers[ACTIONS.USER_INVITE_STATUS_FETCH_FAILURE] = state =>
Object.assign({}, state, {
inviteStatusIsPending: false,
invitesRemaining: null,
invitees: null,
});
export function userReducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}

View file

@ -0,0 +1,64 @@
import { createSelector } from 'reselect';
const selectState = state => state.rewards || {};
export const selectUnclaimedRewardsByType = createSelector(
selectState,
state => state.unclaimedRewardsByType
);
export const selectClaimedRewardsById = createSelector(
selectState,
state => state.claimedRewardsById
);
export const selectClaimedRewards = createSelector(
selectClaimedRewardsById,
byId => Object.values(byId) || []
);
export const selectClaimedRewardsByTransactionId = createSelector(selectClaimedRewards, rewards =>
rewards.reduce((mapParam, reward) => {
const map = mapParam;
map[reward.transaction_id] = reward;
return map;
}, {})
);
export const selectUnclaimedRewards = createSelector(selectState, state => state.unclaimedRewards);
export const selectFetchingRewards = createSelector(selectState, state => !!state.fetching);
export const selectUnclaimedRewardValue = createSelector(selectUnclaimedRewards, rewards =>
rewards.reduce((sum, reward) => sum + reward.reward_amount, 0)
);
export const selectClaimsPendingByType = createSelector(
selectState,
state => state.claimPendingByType
);
const selectIsClaimRewardPending = (state, props) =>
selectClaimsPendingByType(state, props)[props.reward_type];
export const makeSelectIsRewardClaimPending = () =>
createSelector(selectIsClaimRewardPending, isClaiming => isClaiming);
export const selectClaimErrorsByType = createSelector(
selectState,
state => state.claimErrorsByType
);
const selectClaimRewardError = (state, props) =>
selectClaimErrorsByType(state, props)[props.reward_type];
export const makeSelectClaimRewardError = () =>
createSelector(selectClaimRewardError, errorMessage => errorMessage);
const selectRewardByType = (state, rewardType) =>
selectUnclaimedRewards(state).find(reward => reward.reward_type === rewardType);
export const makeSelectRewardByType = () => createSelector(selectRewardByType, reward => reward);
export const makeSelectRewardAmountByType = () =>
createSelector(selectRewardByType, reward => (reward ? reward.reward_amount : 0));

118
src/redux/selectors/user.js Normal file
View file

@ -0,0 +1,118 @@
import { createSelector } from 'reselect';
export const selectState = state => state.user || {};
export const selectAuthenticationIsPending = createSelector(
selectState,
state => state.authenticationIsPending
);
export const selectUserIsPending = createSelector(selectState, state => state.userIsPending);
export const selectUser = createSelector(selectState, state => state.user);
export const selectUserEmail = createSelector(
selectUser,
user => (user ? user.primary_email : null)
);
export const selectUserPhone = createSelector(
selectUser,
user => (user ? user.phone_number : null)
);
export const selectUserCountryCode = createSelector(
selectUser,
user => (user ? user.country_code : null)
);
export const selectEmailToVerify = createSelector(
selectState,
selectUserEmail,
(state, userEmail) => state.emailToVerify || userEmail
);
export const selectPhoneToVerify = createSelector(
selectState,
selectUserPhone,
(state, userPhone) => state.phoneToVerify || userPhone
);
export const selectUserIsRewardApproved = createSelector(
selectUser,
user => user && user.is_reward_approved
);
export const selectEmailNewIsPending = createSelector(
selectState,
state => state.emailNewIsPending
);
export const selectEmailNewErrorMessage = createSelector(
selectState,
state => state.emailNewErrorMessage
);
export const selectPhoneNewErrorMessage = createSelector(
selectState,
state => state.phoneNewErrorMessage
);
export const selectEmailVerifyIsPending = createSelector(
selectState,
state => state.emailVerifyIsPending
);
export const selectEmailVerifyErrorMessage = createSelector(
selectState,
state => state.emailVerifyErrorMessage
);
export const selectPhoneVerifyErrorMessage = createSelector(
selectState,
state => state.phoneVerifyErrorMessage
);
export const selectIdentityVerifyIsPending = createSelector(
selectState,
state => state.identityVerifyIsPending
);
export const selectIdentityVerifyErrorMessage = createSelector(
selectState,
state => state.identityVerifyErrorMessage
);
export const selectUserIsVerificationCandidate = createSelector(
selectUser,
user => user && (!user.has_verified_email || !user.is_identity_verified)
);
export const selectAccessToken = createSelector(selectState, state => state.accessToken);
export const selectUserInviteStatusIsPending = createSelector(
selectState,
state => state.inviteStatusIsPending
);
export const selectUserInvitesRemaining = createSelector(
selectState,
state => state.invitesRemaining
);
export const selectUserInvitees = createSelector(selectState, state => state.invitees);
export const selectUserInviteStatusFailed = createSelector(
selectUserInvitesRemaining,
() => selectUserInvitesRemaining === null
);
export const selectUserInviteNewIsPending = createSelector(
selectState,
state => state.inviteNewIsPending
);
export const selectUserInviteNewErrorMessage = createSelector(
selectState,
state => state.inviteNewErrorMessage
);

112
src/rewards.js Normal file
View file

@ -0,0 +1,112 @@
import { Lbry, doNotify } from 'lbry-redux';
import Lbryio from 'lbryio';
const rewards = {};
rewards.TYPE_NEW_DEVELOPER = 'new_developer';
rewards.TYPE_NEW_USER = 'new_user';
rewards.TYPE_CONFIRM_EMAIL = 'verified_email';
rewards.TYPE_FIRST_CHANNEL = 'new_channel';
rewards.TYPE_FIRST_STREAM = 'first_stream';
rewards.TYPE_MANY_DOWNLOADS = 'many_downloads';
rewards.TYPE_FIRST_PUBLISH = 'first_publish';
rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download';
rewards.TYPE_REFERRAL = 'referral';
rewards.YOUTUBE_CREATOR = 'youtube_creator';
rewards.claimReward = type => {
function requestReward(resolve, reject, params) {
if (!Lbryio.enabled) {
reject(new Error(__('Rewards are not enabled.')));
return;
}
Lbryio.call('reward', 'new', params, 'post').then(reward => {
const message =
reward.reward_notification || `You have claimed a ${reward.reward_amount} LBC reward.`;
// Display global notice
const action = doNotify({
message,
linkText: __('Show All'),
linkTarget: '/rewards',
isError: false,
displayType: ['snackbar'],
});
window.store.dispatch(action);
// Add more events here to display other places
resolve(reward);
}, reject);
}
return new Promise((resolve, reject) => {
Lbry.wallet_unused_address().then(address => {
const params = {
reward_type: type,
wallet_address: address,
};
switch (type) {
case rewards.TYPE_FIRST_CHANNEL:
Lbry.claim_list_mine()
.then(claims => {
const claim = claims
.reverse()
.find(
foundClaim =>
foundClaim.name.length &&
foundClaim.name[0] === '@' &&
foundClaim.txid.length &&
foundClaim.category === 'claim'
);
if (claim) {
params.transaction_id = claim.txid;
requestReward(resolve, reject, params);
} else {
reject(new Error(__('Please create a channel identity first.')));
}
})
.catch(reject);
break;
case rewards.TYPE_FIRST_PUBLISH:
Lbry.claim_list_mine()
.then(claims => {
const claim = claims
.reverse()
.find(
foundClaim =>
foundClaim.name.length &&
foundClaim.name[0] !== '@' &&
foundClaim.txid.length &&
foundClaim.category === 'claim'
);
if (claim) {
params.transaction_id = claim.txid;
requestReward(resolve, reject, params);
} else {
reject(
claims.length
? new Error(
__(
'Please publish something and wait for confirmation by the network to claim this reward.'
)
)
: new Error(__('Please publish something to claim this reward.'))
);
}
})
.catch(reject);
break;
case rewards.TYPE_FIRST_STREAM:
case rewards.TYPE_NEW_USER:
default:
requestReward(resolve, reject, params);
}
});
});
};
export default rewards;