diff --git a/app/src/component/AppNavigator.js b/app/src/component/AppNavigator.js
index e3185c0e..bac1441a 100644
--- a/app/src/component/AppNavigator.js
+++ b/app/src/component/AppNavigator.js
@@ -11,6 +11,7 @@ import SettingsPage from 'page/settings';
import SplashScreen from 'page/splash';
import SubscriptionsPage from 'page/subscriptions';
import TransactionHistoryPage from 'page/transactionHistory';
+import VerificationScreen from 'page/verification';
import WalletPage from 'page/wallet';
import SearchInput from 'component/searchInput';
import {
@@ -212,10 +213,19 @@ const mainStackNavigator = new createStackNavigator({
},
Main: {
screen: drawer
+ },
+ Verification: {
+ screen: VerificationScreen,
+ navigationOptions: {
+ drawerLockMode: 'locked-closed'
+ }
}
}, {
headerMode: 'none'
});
+
+
+
export const AppNavigator = mainStackNavigator;
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
state => state.nav,
diff --git a/app/src/component/dateTime/index.js b/app/src/component/dateTime/index.js
index 3e1ad14a..fd771c83 100644
--- a/app/src/component/dateTime/index.js
+++ b/app/src/component/dateTime/index.js
@@ -1,9 +1,9 @@
import { connect } from 'react-redux';
-import { doFetchBlock, makeSelectBlockDate } from 'lbry-redux';
+import { makeSelectDateForUri } from 'lbry-redux';
import DateTime from './view';
const select = (state, props) => ({
- date: !props.date && props.block ? makeSelectBlockDate(props.block)(state) : props.date,
+ date: props.date || makeSelectDateForUri(props.uri)(state),
});
const perform = dispatch => ({
diff --git a/app/src/component/fileItem/view.js b/app/src/component/fileItem/view.js
index c4b60f9b..28085740 100644
--- a/app/src/component/fileItem/view.js
+++ b/app/src/component/fileItem/view.js
@@ -90,7 +90,7 @@ class FileItem extends React.PureComponent {
{
navigateToUri(navigation, normalizeURI(fullChannelUri));
}} />}
-
+
}
{obscureNsfw && navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
diff --git a/app/src/component/fileListItem/view.js b/app/src/component/fileListItem/view.js
index 65f2c3eb..953a0b56 100644
--- a/app/src/component/fileListItem/view.js
+++ b/app/src/component/fileListItem/view.js
@@ -107,7 +107,7 @@ class FileListItem extends React.PureComponent {
{fileInfo && {this.getStorageForFileInfo(fileInfo)}}
-
+
{fileInfo &&
diff --git a/app/src/component/floatingWalletBalance/index.js b/app/src/component/floatingWalletBalance/index.js
index 11cc3fb5..8282e503 100644
--- a/app/src/component/floatingWalletBalance/index.js
+++ b/app/src/component/floatingWalletBalance/index.js
@@ -1,11 +1,14 @@
import { connect } from 'react-redux';
-import { selectTotalBalance } from 'lbry-redux';
+import { makeSelectClientSetting } from 'redux/selectors/settings';
+import { selectBalance } from 'lbry-redux';
import { selectUnclaimedRewardValue } from 'lbryinc';
+import Constants from 'constants';
import FloatingWalletBalance from './view';
const select = state => ({
- balance: selectTotalBalance(state),
+ balance: selectBalance(state),
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
+ rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
});
export default connect(select, null)(FloatingWalletBalance);
diff --git a/app/src/component/floatingWalletBalance/view.js b/app/src/component/floatingWalletBalance/view.js
index cb20b441..49a1e185 100644
--- a/app/src/component/floatingWalletBalance/view.js
+++ b/app/src/component/floatingWalletBalance/view.js
@@ -14,11 +14,11 @@ type Props = {
class FloatingWalletBalance extends React.PureComponent {
render() {
- const { balance, navigation, unclaimedRewardAmount } = this.props;
+ const { balance, navigation, rewardsNotInterested, unclaimedRewardAmount } = this.props;
return (
- {unclaimedRewardAmount > 0 &&
+ {(!rewardsNotInterested && unclaimedRewardAmount > 0) &&
navigation && navigation.navigate({ routeName: 'Rewards' })} >
diff --git a/app/src/component/rewardCard/view.js b/app/src/component/rewardCard/view.js
index 4d96b099..10c14e00 100644
--- a/app/src/component/rewardCard/view.js
+++ b/app/src/component/rewardCard/view.js
@@ -72,16 +72,7 @@ class RewardCard extends React.PureComponent {
}
}}>
- {!isPending && {
- if (!claimed) {
- this.onClaimPress();
- }
- }}>
-
- }
- {isPending && }
+
{reward.reward_title}
@@ -92,6 +83,16 @@ class RewardCard extends React.PureComponent {
error={'The transaction URL could not be opened'} />}
+ {!isPending && {
+ if (!claimed) {
+ this.onClaimPress();
+ }
+ }}>
+
+ }
+ {isPending && }
{reward.reward_amount}
LBC
diff --git a/app/src/component/rewardEnrolment/index.js b/app/src/component/rewardEnrolment/index.js
new file mode 100644
index 00000000..6a345c99
--- /dev/null
+++ b/app/src/component/rewardEnrolment/index.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux';
+import { doToast } from 'lbry-redux';
+import { doSetClientSetting } from 'redux/actions/settings';
+import { doRewardList, selectUnclaimedRewardValue, selectFetchingRewards, selectUser } from 'lbryinc';
+import RewardEnrolment from './view';
+
+const select = state => ({
+ unclaimedRewardAmount: selectUnclaimedRewardValue(state),
+ fetching: selectFetchingRewards(state),
+ user: selectUser(state)
+});
+
+const perform = dispatch => ({
+ fetchRewards: () => dispatch(doRewardList()),
+ notify: data => dispatch(doToast(data)),
+ setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
+});
+
+export default connect(select, perform)(RewardEnrolment);
diff --git a/app/src/component/rewardEnrolment/view.js b/app/src/component/rewardEnrolment/view.js
new file mode 100644
index 00000000..d04133d8
--- /dev/null
+++ b/app/src/component/rewardEnrolment/view.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import { NativeModules, Text, TouchableOpacity, View } from 'react-native';
+import AsyncStorage from '@react-native-community/async-storage';
+import Button from 'component/button';
+import Constants from 'constants';
+import Link from 'component/link';
+import Colors from 'styles/colors';
+import Icon from 'react-native-vector-icons/FontAwesome5';
+import rewardStyle from 'styles/reward';
+
+class RewardEnrolment extends React.Component {
+ componentDidMount() {
+ this.props.fetchRewards();
+ }
+
+ onNotInterestedPressed = () => {
+ const { navigation, setClientSetting } = this.props;
+ setClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED, true);
+ navigation.navigate({ routeName: 'DiscoverStack' });
+ }
+
+ onEnrollPressed = () => {
+ const { navigation } = this.props;
+ navigation.navigate({ routeName: 'Verification' })
+ }
+
+ render() {
+ const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
+
+ return (
+
+
+
+
+ {unclaimedRewardAmount} unclaimed credits
+
+
+
+
+ LBRY credits allow you to purchase content, publish content, and influence the network. You can start earning credits by watching videos on LBRY.
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default RewardEnrolment;
diff --git a/app/src/component/walletBalance/index.js b/app/src/component/walletBalance/index.js
index 1d205a52..9f69e655 100644
--- a/app/src/component/walletBalance/index.js
+++ b/app/src/component/walletBalance/index.js
@@ -1,9 +1,9 @@
import { connect } from 'react-redux';
-import { selectTotalBalance } from 'lbry-redux';
+import { selectBalance } from 'lbry-redux';
import WalletBalance from './view';
const select = state => ({
- balance: selectTotalBalance(state),
+ balance: selectBalance(state),
});
export default connect(select, null)(WalletBalance);
diff --git a/app/src/component/walletRewardsDriver/index.js b/app/src/component/walletRewardsDriver/index.js
new file mode 100644
index 00000000..b328bf3b
--- /dev/null
+++ b/app/src/component/walletRewardsDriver/index.js
@@ -0,0 +1,6 @@
+import { connect } from 'react-redux';
+import WalletRewardsDriver from './view';
+
+const select = state => ({});
+
+export default connect(select, null)(WalletRewardsDriver);
diff --git a/app/src/component/walletRewardsDriver/view.js b/app/src/component/walletRewardsDriver/view.js
new file mode 100644
index 00000000..7463fce3
--- /dev/null
+++ b/app/src/component/walletRewardsDriver/view.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Text, TouchableOpacity } from 'react-native';
+import walletStyle from 'styles/wallet';
+
+class WalletRewardsDriver extends React.PureComponent {
+ render() {
+ const { navigation } = this.props;
+
+ return (
+ navigation.navigate('Rewards')}>
+ Earn credits while using the LBRY app. Tap to get started.
+
+ );
+ }
+}
+
+export default WalletRewardsDriver;
diff --git a/app/src/constants.js b/app/src/constants.js
index 07ab3e5b..d50fdfc7 100644
--- a/app/src/constants.js
+++ b/app/src/constants.js
@@ -4,6 +4,12 @@ const Constants = {
FIRST_RUN_PAGE_WALLET: "wallet",
FIRST_RUN_PAGE_SKIP_ACCOUNT: "skip-account",
+ VERIFY_PAGE_EMAIL: "email-verify",
+ VERIFY_PAGE_PHONE_NUMBER: "phone-number-verify",
+
+ PHASE_COLLECTION: "collection",
+ PHASE_VERIFICATION: "verification",
+
CONTENT_TAB: "content",
ABOUT_TAB: "about",
@@ -16,6 +22,8 @@ const Constants = {
SETTING_SUBSCRIPTIONS_VIEW_MODE: "subscriptionsViewMode",
SETTING_RATING_REMINDER_LAST_SHOWN: "ratingReminderLastShown",
SETTING_RATING_REMINDER_DISABLED: "ratingReminderDisabled",
+ SETTING_BACKUP_DISMISSED: "backupDismissed",
+ SETTING_REWARDS_NOT_INTERESTED: "rewardsNotInterested",
ACTION_DELETE_COMPLETED_BLOBS: "DELETE_COMPLETED_BLOBS",
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED",
diff --git a/app/src/page/file/view.js b/app/src/page/file/view.js
index 1b1bc2f7..5dcce737 100644
--- a/app/src/page/file/view.js
+++ b/app/src/page/file/view.js
@@ -601,7 +601,7 @@ class FilePage extends React.PureComponent {
diff --git a/app/src/page/rewards/index.js b/app/src/page/rewards/index.js
index c45887d6..3f5d984a 100644
--- a/app/src/page/rewards/index.js
+++ b/app/src/page/rewards/index.js
@@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import {
+ doClaimRewardType,
doRewardList,
selectEmailVerifyErrorMessage,
selectEmailVerifyIsPending,
@@ -23,6 +24,7 @@ const select = state => ({
});
const perform = dispatch => ({
+ claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
fetchRewards: () => dispatch(doRewardList()),
notify: data => dispatch(doToast(data)),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_REWARDS))
diff --git a/app/src/page/rewards/view.js b/app/src/page/rewards/view.js
index 39beda43..800d86fd 100644
--- a/app/src/page/rewards/view.js
+++ b/app/src/page/rewards/view.js
@@ -14,6 +14,7 @@ import PhoneNumberRewardSubcard from 'component/phoneNumberRewardSubcard';
import EmailRewardSubcard from 'component/emailRewardSubcard';
import PageHeader from 'component/pageHeader';
import RewardCard from 'component/rewardCard';
+import RewardEnrolment from 'component/rewardEnrolment';
import RewardSummary from 'component/rewardSummary';
import UriBar from 'component/uriBar';
import rewardStyle from 'styles/reward';
@@ -24,7 +25,8 @@ class RewardsPage extends React.PureComponent {
isIdentityVerified: false,
isRewardApproved: false,
verifyRequestStarted: false,
- revealVerification: false
+ revealVerification: true,
+ firstRewardClaimed: false
};
scrollView = null;
@@ -43,7 +45,8 @@ class RewardsPage extends React.PureComponent {
}
componentWillReceiveProps(nextProps) {
- const { emailVerifyErrorMessage, emailVerifyPending, user } = nextProps;
+ const { emailVerifyErrorMessage, emailVerifyPending, rewards, user } = nextProps;
+ const { claimReward } = this.props;
if (emailVerifyPending) {
this.setState({ verifyRequestStarted: true });
}
@@ -63,6 +66,17 @@ class RewardsPage extends React.PureComponent {
isRewardApproved: (user && user.is_reward_approved)
});
}
+
+ if (rewards && rewards.length && this.state.isRewardApproved && !this.state.firstRewardClaimed) {
+ // claim new_user and new_mobile rewards
+ for (let i = 0; i < rewards.length; i++) {
+ const { reward_type: type } = rewards[i];
+ if ('new_user' === type || 'new_mobile' === type) {
+ claimReward(rewards[i]);
+ }
+ }
+ this.setState({ firstRewardClaimed: true });
+ }
}
renderVerification() {
@@ -70,23 +84,12 @@ class RewardsPage extends React.PureComponent {
return null;
}
- if (!this.state.isEmailVerified || !this.state.isIdentityVerified) {
- return (
-
- Humans Only
- Rewards are for human beings only. You'll have to prove you're one of us before you can claim any rewards.
- {!this.state.isEmailVerified && }
- {this.state.isEmailVerified && !this.state.isIdentityVerified && }
-
- );
- }
-
if (this.state.isEmailVerified && this.state.isIdentityVerified && !this.state.isRewardApproved) {
return (
Manual Reward Verification
- You need to be manually verified before you can start claiming rewards. Please request to be verified on the .
+ You need to be manually verified before you can start claiming rewards. Please request to be verified on the .
);
@@ -153,16 +156,18 @@ class RewardsPage extends React.PureComponent {
return (
- this.scrollView = ref}
- keyboardShouldPersistTaps={'handled'}
- style={rewardStyle.scrollContainer}
- contentContainerStyle={rewardStyle.scrollContentContainer}>
-
- {this.state.revealVerification && this.renderVerification()}
- {this.renderUnclaimedRewards()}
- {this.renderClaimedRewards()}
-
+ {(!this.state.isEmailVerified || !this.state.isIdentityVerified || !this.state.isRewardApproved) &&
+ }
+
+ {(this.state.isEmailVerified && this.state.isIdentityVerified && this.state.isRewardApproved) &&
+ this.scrollView = ref}
+ keyboardShouldPersistTaps={'handled'}
+ style={rewardStyle.scrollContainer}
+ contentContainerStyle={rewardStyle.scrollContentContainer}>
+ {this.renderUnclaimedRewards()}
+ {this.renderClaimedRewards()}
+ }
);
}
diff --git a/app/src/page/splash/index.js b/app/src/page/splash/index.js
index b3c5adf0..58d72be4 100644
--- a/app/src/page/splash/index.js
+++ b/app/src/page/splash/index.js
@@ -1,5 +1,5 @@
import { connect } from 'react-redux';
-import { doTotalBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux';
+import { doBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux';
import {
doAuthenticate,
doBlackListedOutpointsSubscribe,
@@ -23,7 +23,7 @@ const select = state => ({
const perform = dispatch => ({
authenticate: (appVersion, os) => dispatch(doAuthenticate(appVersion, os)),
- totalBalanceSubscribe: () => dispatch(doTotalBalanceSubscribe()),
+ balanceSubscribe: () => dispatch(doBalanceSubscribe()),
blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()),
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()),
diff --git a/app/src/page/splash/view.js b/app/src/page/splash/view.js
index 1df1e394..b9a4b7e3 100644
--- a/app/src/page/splash/view.js
+++ b/app/src/page/splash/view.js
@@ -137,7 +137,7 @@ class SplashScreen extends React.PureComponent {
finishSplashScreen = () => {
const {
authenticate,
- totalBalanceSubscribe,
+ balanceSubscribe,
blacklistedOutpointsSubscribe,
checkSubscriptionsInit,
updateBlockHeight,
@@ -147,7 +147,7 @@ class SplashScreen extends React.PureComponent {
Lbry.resolve({ urls: 'lbry://one' }).then(() => {
// Leave the splash screen
- totalBalanceSubscribe();
+ balanceSubscribe();
blacklistedOutpointsSubscribe();
checkSubscriptionsInit();
updateBlockHeight();
diff --git a/app/src/page/verification/index.js b/app/src/page/verification/index.js
new file mode 100644
index 00000000..427c188d
--- /dev/null
+++ b/app/src/page/verification/index.js
@@ -0,0 +1,42 @@
+import { connect } from 'react-redux';
+import { doToast } from 'lbry-redux';
+import {
+ doUserEmailNew,
+ doUserEmailToVerify,
+ doUserResendVerificationEmail,
+ doUserPhoneNew,
+ doUserPhoneVerify,
+ selectPhoneNewErrorMessage,
+ selectPhoneNewIsPending,
+ selectPhoneToVerify,
+ selectPhoneVerifyIsPending,
+ selectPhoneVerifyErrorMessage,
+ selectEmailNewErrorMessage,
+ selectEmailNewIsPending,
+ selectEmailToVerify,
+ selectUser,
+} from 'lbryinc';
+import Verification from './view';
+
+const select = (state) => ({
+ emailToVerify: selectEmailToVerify(state),
+ emailNewErrorMessage: selectEmailNewErrorMessage(state),
+ emailNewPending: selectEmailNewIsPending(state),
+ user: selectUser(state),
+ phoneVerifyErrorMessage: selectPhoneVerifyErrorMessage(state),
+ phoneVerifyIsPending: selectPhoneVerifyIsPending(state),
+ phone: selectPhoneToVerify(state),
+ phoneNewErrorMessage: selectPhoneNewErrorMessage(state),
+ phoneNewIsPending: selectPhoneNewIsPending(state),
+});
+
+const perform = dispatch => ({
+ addUserEmail: email => dispatch(doUserEmailNew(email)),
+ addUserPhone: (phone, country_code) => dispatch(doUserPhoneNew(phone, country_code)),
+ verifyPhone: (verificationCode) => dispatch(doUserPhoneVerify(verificationCode)),
+ notify: data => dispatch(doToast(data)),
+ setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
+ resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email))
+});
+
+export default connect(select, perform)(Verification);
diff --git a/app/src/page/verification/internal/email-verify-page.js b/app/src/page/verification/internal/email-verify-page.js
new file mode 100644
index 00000000..f958b609
--- /dev/null
+++ b/app/src/page/verification/internal/email-verify-page.js
@@ -0,0 +1,132 @@
+import React from 'react';
+import { Lbry } from 'lbry-redux';
+import { ActivityIndicator, View, Text, TextInput } from 'react-native';
+import AsyncStorage from '@react-native-community/async-storage';
+import Button from 'component/button';
+import Link from 'component/link';
+import Colors from 'styles/colors';
+import Constants from 'constants';
+import firstRunStyle from 'styles/firstRun';
+import rewardStyle from 'styles/reward';
+
+class EmailVerifyPage extends React.PureComponent {
+ state = {
+ email: null,
+ phase: Constants.PHASE_COLLECTION,
+ placeholder: 'you@example.com',
+ verifyStarted: false,
+ previousEmail: null
+ }
+
+ handleChangeText = (text) => {
+ this.setState({ email: text });
+ AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { emailNewErrorMessage, emailNewPending, emailToVerify } = nextProps;
+ const { notify } = this.props;
+
+ if (this.state.verifyStarted && !emailNewPending) {
+ if (emailNewErrorMessage) {
+ notify({ message: String(emailNewErrorMessage), isError: true });
+ this.setState({ verifyStarted: false });
+ } else {
+ this.setState({ phase: Constants.PHASE_VERIFICATION });
+ //notify({ message: 'Please follow the instructions in the email sent to your address to continue.' });
+ AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
+ }
+ }
+ }
+
+ onSendVerificationPressed = () => {
+ const { addUserEmail, emailNewPending, notify, resendVerificationEmail } = this.props;
+
+ if (emailNewPending) {
+ return;
+ }
+
+ const { email } = this.state;
+ if (!email || email.trim().length === 0 || email.indexOf('@') === -1) {
+ return notify({
+ message: 'Please provide a valid email address to continue.',
+ });
+ }
+
+ if (this.state.previousEmail === this.state.email) {
+ // resend
+ resendVerificationEmail(this.state.email);
+ AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
+ this.setState({ verifyStarted: true, phase: Constants.PHASE_VERIFICATION });
+ return;
+ }
+
+ this.setState({ verifyStarted: true });
+ addUserEmail(email);
+ }
+
+ onResendPressed = () => {
+ const { resendVerificationEmail, notify } = this.props;
+ // resend verification email if there was one previously set (and it wasn't changed)
+ resendVerificationEmail(this.state.email);
+ AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
+ notify({ message: 'Please follow the instructions in the email sent to your address to continue.' });
+ }
+
+ onEditPressed = () => {
+ this.setState({ verifyStarted: false, phase: Constants.PHASE_COLLECTION, previousEmail: this.state.email });
+ }
+
+ render() {
+ const { emailNewPending } = this.props;
+
+ return (
+
+ {(Constants.PHASE_COLLECTION === this.state.phase) ? 'Email' : 'Verify Email'}
+ {(Constants.PHASE_COLLECTION === this.state.phase) &&
+
+ Please provide an email address.
+ this.handleChangeText(text)}
+ onFocus={() => {
+ if (!this.state.email || this.state.email.length === 0) {
+ this.setState({ placeholder: '' });
+ }
+ }}
+ onBlur={() => {
+ if (!this.state.email || this.state.email.length === 0) {
+ this.setState({ placeholder: 'you@example.com' });
+ }
+ }}
+ />
+
+ {!this.state.verifyStarted &&
+ }
+ {this.state.verifyStarted && emailNewPending &&
+ }
+
+ }
+
+ {(Constants.PHASE_VERIFICATION === this.state.phase) &&
+
+ An email has been sent to {this.state.email}. Please follow the instructions in the message to verify your email address.
+
+
+
+
+
+
+ }
+
+ );
+ }
+}
+
+export default EmailVerifyPage;
diff --git a/app/src/page/verification/internal/manual-verify-page.js b/app/src/page/verification/internal/manual-verify-page.js
new file mode 100644
index 00000000..250b9c2c
--- /dev/null
+++ b/app/src/page/verification/internal/manual-verify-page.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Lbry } from 'lbry-redux';
+import { ActivityIndicator, View, Text, TextInput } from 'react-native';
+import AsyncStorage from '@react-native-community/async-storage';
+import Button from 'component/button';
+import Link from 'component/link';
+import Colors from 'styles/colors';
+import Constants from 'constants';
+import firstRunStyle from 'styles/firstRun';
+import rewardStyle from 'styles/reward';
+
+class ManualVerifyPage extends React.PureComponent {
+ render() {
+ return (
+
+ Manual Reward Verification
+ You need to be manually verified before you can start claiming rewards. Please request to be verified on the .
+
+ );
+ }
+}
+
+export default ManualVerifyPage;
diff --git a/app/src/page/verification/internal/phone-verify-page.js b/app/src/page/verification/internal/phone-verify-page.js
new file mode 100644
index 00000000..e698ba5e
--- /dev/null
+++ b/app/src/page/verification/internal/phone-verify-page.js
@@ -0,0 +1,235 @@
+// @flow
+import React from 'react';
+import {
+ ActivityIndicator,
+ DeviceEventEmitter,
+ NativeModules,
+ StyleSheet,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import AsyncStorage from '@react-native-community/async-storage';
+import Button from 'component/button';
+import Colors from 'styles/colors';
+import Constants from 'constants';
+import CountryPicker from 'react-native-country-picker-modal';
+import Icon from 'react-native-vector-icons/FontAwesome5';
+import Link from 'component/link';
+import PhoneInput from 'react-native-phone-input';
+import firstRunStyle from 'styles/firstRun';
+import rewardStyle from 'styles/reward';
+
+class PhoneVerifyPage extends React.PureComponent {
+ phoneInput = null;
+
+ picker = null;
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ canReceiveSms: false,
+ cca2: 'US',
+ codeVerifyStarted: false,
+ codeVerifySuccessful: false,
+ countryCode: null,
+ newPhoneAdded: false,
+ number: null,
+ phoneVerifyFailed: false,
+ verificationCode: null,
+ phase: Constants.PHASE_COLLECTION
+ };
+ }
+
+ componentDidMount() {
+ const { phone } = this.props;
+ if (phone && String(phone).trim().length > 0) {
+ this.setState({ newPhoneAdded: true, phase: Constants.PHASE_VERIFICATION });
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const {
+ phoneVerifyIsPending,
+ phoneVerifyErrorMessage,
+ notify,
+ phoneNewErrorMessage,
+ phoneNewIsPending,
+ onPhoneVerifySuccessful
+ } = this.props;
+
+ if (!phoneNewIsPending && (phoneNewIsPending !== prevProps.phoneNewIsPending)) {
+ if (phoneNewErrorMessage) {
+ notify({ message: String(phoneNewErrorMessage) });
+ this.setState({ phoneVerifyFailed: true });
+ } else {
+ this.setState({ newPhoneAdded: true, phase: Constants.PHASE_VERIFICATION, phoneVerifyFailed: false });
+ }
+ }
+ if (!phoneVerifyIsPending && (phoneVerifyIsPending !== prevProps.phoneVerifyIsPending)) {
+ if (phoneVerifyErrorMessage) {
+ notify({ message: String(phoneVerifyErrorMessage) });
+ this.setState({ codeVerifyStarted: false, phoneVerifyFailed: true });
+ } else {
+ notify({ message: 'Your phone number was successfully verified.' });
+ this.setState({ codeVerifySuccessful: true, phoneVerifyFailed: false });
+ if (onPhoneVerifySuccessful) {
+ onPhoneVerifySuccessful();
+ }
+ }
+ }
+ }
+
+ onEditPressed = () => {
+ this.setState({ newPhoneAdded: false, phase: Constants.PHASE_COLLECTION, phoneVerifyFailed: false });
+ }
+
+ receiveVerificationCode = (evt) => {
+ if (!this.state.newPhoneAdded || this.state.codeVerifySuccessful) {
+ return;
+ }
+
+ const { verifyPhone } = this.props;
+ this.setState({ codeVerifyStarted: true });
+ verifyPhone(evt.code);
+ }
+
+ onSendTextPressed = () => {
+ const { addUserPhone, notify } = this.props;
+
+ if (!this.phoneInput.isValidNumber()) {
+ return notify({
+ message: 'Please provide a valid telephone number.',
+ });
+ }
+
+ this.setState({ phoneVerifyFailed: false });
+ const countryCode = this.phoneInput.getCountryCode();
+ const number = this.phoneInput.getValue().replace('+' + countryCode, '');
+ this.setState({ countryCode, number });
+ addUserPhone(number, countryCode);
+ }
+
+ onVerifyPressed = () => {
+ if (this.state.codeVerifyStarted) {
+ return;
+ }
+
+ const { verifyPhone } = this.props;
+ this.setState({ codeVerifyStarted: true, phoneVerifyFailed: false });
+ verifyPhone(this.state.verificationCode);
+ }
+
+ onPressFlag = () => {
+ if (this.picker) {
+ this.picker.openModal();
+ }
+ }
+
+ selectCountry(country) {
+ this.phoneInput.selectCountry(country.cca2.toLowerCase());
+ this.setState({ cca2: country.cca2 });
+ }
+
+ handleChangeText = (text) => {
+ this.setState({ verificationCode: text });
+ };
+
+ render() {
+ const {
+ phoneVerifyIsPending,
+ phoneVerifyErrorMessage,
+ phone,
+ phoneErrorMessage,
+ phoneNewIsPending
+ } = this.props;
+
+ return (
+
+ {this.state.phase === Constants.PHASE_VERIFICATION ? 'Verify ' : '' }Phone Number
+
+
+ {this.state.phase == Constants.PHASE_COLLECTION &&
+
+ Please provide a phone number to prevent fraud.
+ { this.phoneInput = ref; }}
+ style={StyleSheet.flatten(rewardStyle.phoneInput)}
+ textProps={{ placeholder: '(phone number)' }}
+ textStyle={StyleSheet.flatten(rewardStyle.phoneInputText)}
+ onPressFlag={this.onPressFlag} />
+
+
+ {!phoneNewIsPending &&
+ }
+ {phoneNewIsPending &&
+ }
+
+ }
+
+ {this.state.phase === Constants.PHASE_VERIFICATION &&
+
+ {!phoneVerifyIsPending && !this.codeVerifyStarted &&
+
+
+ Please enter the verification code sent to {phone}.
+
+ this.handleChangeText(text)}
+ />
+
+
+
+
+
+ }
+ {phoneVerifyIsPending &&
+
+ Verifying your phone number...
+
+ }
+
+ }
+ {this.state.phoneVerifyFailed &&
+
+
+ Sorry, we were unable to verify your phone number. Please go to for manual verification if this keeps happening.
+
+ }
+
+
+ { this.picker = picker; }}
+ cca2={this.state.cca2}
+ filterable={true}
+ onChange={value => this.selectCountry(value)}
+ showCallingCode={true}
+ translation="eng">
+
+
+
+ );
+ }
+};
+
+export default PhoneVerifyPage;
diff --git a/app/src/page/verification/view.js b/app/src/page/verification/view.js
new file mode 100644
index 00000000..c0312ed2
--- /dev/null
+++ b/app/src/page/verification/view.js
@@ -0,0 +1,140 @@
+import React from 'react';
+import { Lbry } from 'lbry-redux';
+import {
+ ActivityIndicator,
+ Linking,
+ NativeModules,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import { NavigationActions, StackActions } from 'react-navigation';
+import AsyncStorage from '@react-native-community/async-storage';
+import Colors from 'styles/colors';
+import Constants from 'constants';
+import EmailVerifyPage from './internal/email-verify-page';
+import ManualVerifyPage from './internal/manual-verify-page';
+import PhoneVerifyPage from './internal/phone-verify-page';
+import firstRunStyle from 'styles/firstRun';
+
+class VerificationScreen extends React.PureComponent {
+ state = {
+ currentPage: null,
+ emailSubmitted: false,
+ isFirstRun: false,
+ launchUrl: null,
+ showSkip: false,
+ skipAccountConfirmed: false,
+ showBottomContainer: true,
+ walletPassword: null,
+ isEmailVerified: false,
+ isIdentityVerified: false,
+ isRewardApproved: false
+ };
+
+ componentDidMount() {
+ const { user } = this.props;
+ this.checkVerificationStatus(user);
+ }
+
+ checkVerificationStatus = (user) => {
+ const { navigation } = this.props;
+
+ this.setState({
+ isEmailVerified: (user && user.primary_email && user.has_verified_email),
+ isIdentityVerified: (user && user.is_identity_verified),
+ isRewardApproved: (user && user.is_reward_approved)
+ }, () => {
+ if (!this.state.isEmailVerified) {
+ this.setState({ currentPage: 'emailVerify' });
+ }
+ if (this.state.isEmailVerified && !this.state.isIdentityVerified) {
+ this.setState({ currentPage: 'phoneVerify' });
+ }
+ if (this.state.isEmailVerified && this.state.isIdentityVerified && !this.state.isRewardApproved) {
+ this.setState({ currentPage: 'manualVerify' });
+ }
+
+ if (this.state.isEmailVerified && this.state.isIdentityVerified && this.state.isRewardApproved) {
+ // verification steps already completed
+ // simply navigate back to the rewards page
+ navigation.goBack();
+ }
+ });
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { user } = nextProps;
+ this.checkVerificationStatus(user);
+ }
+
+ onCloseButtonPressed = () => {
+ const { navigation } = this.props;
+ navigation.goBack();
+ }
+
+ render() {
+ const {
+ addUserEmail,
+ emailNewErrorMessage,
+ emailNewPending,
+ emailToVerify,
+ navigation,
+ notify,
+ addUserPhone,
+ phone,
+ phoneVerifyIsPending,
+ phoneVerifyErrorMessage,
+ phoneNewIsPending,
+ phoneNewErrorMessage,
+ resendVerificationEmail,
+ verifyPhone
+ } = this.props;
+
+ let page = null;
+ switch (this.state.currentPage) {
+ case 'emailVerify':
+ page = (
+
+ );
+ break;
+ case 'phoneVerify':
+ page = (
+
+ );
+ break;
+ case 'manualVerify':
+ page = (
+
+ );
+ }
+
+ return (
+
+ {page}
+
+
+ x
+
+
+ );
+ }
+}
+
+export default VerificationScreen;
diff --git a/app/src/page/wallet/index.js b/app/src/page/wallet/index.js
index 028f990e..7ab4eef6 100644
--- a/app/src/page/wallet/index.js
+++ b/app/src/page/wallet/index.js
@@ -2,13 +2,17 @@ import { connect } from 'react-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doPushDrawerStack } from 'redux/actions/drawer';
+import { selectBalance } from 'lbry-redux';
import { doGetSync, selectUser } from 'lbryinc';
import Constants from 'constants';
import WalletPage from './view';
const select = state => ({
user: selectUser(state),
+ balance: selectBalance(state),
understandsRisks: makeSelectClientSetting(Constants.SETTING_ALPHA_UNDERSTANDS_RISKS)(state),
+ backupDismissed: makeSelectClientSetting(Constants.SETTING_BACKUP_DISMISSED)(state),
+ rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
});
const perform = dispatch => ({
diff --git a/app/src/page/wallet/view.js b/app/src/page/wallet/view.js
index 3e094d64..37d39214 100644
--- a/app/src/page/wallet/view.js
+++ b/app/src/page/wallet/view.js
@@ -1,6 +1,7 @@
import React from 'react';
import { NativeModules, ScrollView, Text, View } from 'react-native';
import TransactionListRecent from 'component/transactionListRecent';
+import WalletRewardsDriver from 'component/walletRewardsDriver';
import WalletAddress from 'component/walletAddress';
import WalletBalance from 'component/walletBalance';
import WalletSend from 'component/walletSend';
@@ -20,8 +21,20 @@ class WalletPage extends React.PureComponent {
}
}
+ onDismissBackupPressed = () => {
+ const { setClientSetting } = this.props;
+ setClientSetting(Constants.SETTING_BACKUP_DISMISSED, true);
+ }
+
render() {
- const { understandsRisks, setClientSetting, navigation } = this.props;
+ const {
+ balance,
+ backupDismissed,
+ rewardsNotInterested,
+ understandsRisks,
+ setClientSetting,
+ navigation
+ } = this.props;
if (!understandsRisks) {
return (
@@ -41,17 +54,20 @@ class WalletPage extends React.PureComponent {
return (
-
+
+ {!backupDismissed &&
Please backup your wallet file using the instructions at .
-
+
+ }
+ {(!rewardsNotInterested) && (!balance || balance === 0) && }
-
+
);
diff --git a/app/src/styles/firstRun.js b/app/src/styles/firstRun.js
index bb17877a..45f2cec8 100644
--- a/app/src/styles/firstRun.js
+++ b/app/src/styles/firstRun.js
@@ -129,6 +129,20 @@ const firstRunStyle = StyleSheet.create({
},
titleIcon: {
marginTop: 8
+ },
+ closeButton: {
+ position: 'absolute',
+ top: 8,
+ right: 8,
+ width: 48,
+ height: 48,
+ borderRadius: 48,
+ justifyContent: 'center'
+ },
+ closeButtonText: {
+ alignSelf: 'center',
+ color: Colors.White,
+ fontSize: 16
}
});
diff --git a/app/src/styles/reward.js b/app/src/styles/reward.js
index 0ac26c18..3a17fbb9 100644
--- a/app/src/styles/reward.js
+++ b/app/src/styles/reward.js
@@ -27,6 +27,24 @@ const rewardStyle = StyleSheet.create({
justifyContent: 'center',
flexDirection: 'row'
},
+ enrollContainer: {
+ flex: 1,
+ marginTop: 76,
+ marginLeft: 16,
+ marginRight: 16,
+ marginBottom: 16,
+ padding: 24,
+ backgroundColor: Colors.LbryGreen
+ },
+ onboarding: {
+ marginTop: 36
+ },
+ enrollDescText: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 18,
+ lineHeight: 28,
+ color: Colors.White
+ },
rewardsContainer: {
flex: 1
},
@@ -101,21 +119,32 @@ const rewardStyle = StyleSheet.create({
bottomMarginLarge: {
marginBottom: 24
},
+ leftRightMargin: {
+ marginLeft: 32,
+ marginRight: 32
+ },
link: {
color: Colors.LbryGreen,
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
},
textLink: {
+ color: Colors.White
+ },
+ underlinedTextLink: {
+ color: Colors.White,
+ textDecorationLine: 'underline'
+ },
+ greenLink: {
color: Colors.LbryGreen
},
leftCol: {
- width: '15%',
+ width: '5%',
alignItems: 'center',
paddingLeft: 6
},
midCol: {
- width: '65%'
+ width: '75%'
},
rightCol: {
width: '18%',
@@ -178,12 +207,14 @@ const rewardStyle = StyleSheet.create({
paddingRight: 4
},
phoneInput: {
- marginLeft: 8
+ marginLeft: 32,
+ marginRight: 32
},
phoneInputText: {
fontFamily: 'Inter-UI-Regular',
fontSize: 16,
- letterSpacing: 1.3
+ letterSpacing: 1.3,
+ color: Colors.White
},
verifyingText: {
fontFamily: 'Inter-UI-Regular',
@@ -193,8 +224,11 @@ const rewardStyle = StyleSheet.create({
},
verificationCodeInput: {
fontFamily: 'Inter-UI-Regular',
+ color: Colors.White,
fontSize: 24,
- letterSpacing: 12
+ letterSpacing: 12,
+ marginLeft: 32,
+ marginRight: 32
},
loading: {
alignSelf: 'flex-start'
@@ -202,12 +236,24 @@ const rewardStyle = StyleSheet.create({
smsPermissionContainer: {
marginBottom: 32
},
- dismissButton: {
- alignSelf: 'flex-end',
+ buttonRow: {
+ width: '100%',
+ position: 'absolute',
+ alignItems: 'center',
+ left: 24,
+ bottom: 24,
+ flexDirection: 'row',
+ justifyContent: 'space-between'
+ },
+ notInterestedLink: {
+ fontSize: 14,
+ fontFamily: 'Inter-UI-Regular',
+ color: Colors.White
+ },
+ enrollButton: {
backgroundColor: Colors.White,
paddingLeft: 16,
- paddingRight: 16,
- marginTop: 8,
+ paddingRight: 16
},
customCodeInput: {
fontFamily: 'Inter-UI-Regular',
@@ -221,7 +267,41 @@ const rewardStyle = StyleSheet.create({
backgroundColor: Colors.LbryGreen
},
failureFootnote: {
- marginTop: 12
+ marginTop: 32,
+ marginLeft: 32,
+ marginRight: 32
+ },
+ buttonContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ width: '100%',
+ paddingLeft: 32,
+ paddingRight: 32,
+ marginTop: 16
+ },
+ verificationTitle: {
+ fontSize: 32,
+ color: Colors.White,
+ fontFamily: 'Inter-UI-Regular',
+ marginLeft: 32,
+ marginRight: 32,
+ marginBottom: 24
+ },
+ verificationButton: {
+ backgroundColor: Colors.White,
+ paddingLeft: 16,
+ paddingRight: 16
+ },
+ verificationLink: {
+ color: Colors.White,
+ fontSize: 14
+ },
+ paragraphText: {
+ fontFamily: 'Inter-UI-Regular',
+ color: Colors.White,
+ fontSize: 12,
+ lineHeight: 16
}
});
diff --git a/app/src/styles/wallet.js b/app/src/styles/wallet.js
index b33da227..c9f0b4a9 100644
--- a/app/src/styles/wallet.js
+++ b/app/src/styles/wallet.js
@@ -5,6 +5,9 @@ const walletStyle = StyleSheet.create({
container: {
backgroundColor: Colors.PageBackground
},
+ scrollContainer: {
+ marginTop: 60
+ },
row: {
flexDirection: 'row',
justifyContent: 'space-between',
@@ -40,7 +43,7 @@ const walletStyle = StyleSheet.create({
backgroundColor: Colors.Orange,
padding: 16,
marginLeft: 16,
- marginTop: 76,
+ marginTop: 16,
marginRight: 16
},
transactionsCard: {
@@ -142,7 +145,8 @@ const walletStyle = StyleSheet.create({
color: Colors.White,
fontFamily: 'Inter-UI-Regular',
fontSize: 16,
- lineHeight: 24
+ lineHeight: 24,
+ marginBottom: 8
},
understand: {
marginLeft: 16,
@@ -168,6 +172,19 @@ const walletStyle = StyleSheet.create({
},
transactionHistoryScroll: {
marginTop: 60
+ },
+ rewardDriverCard: {
+ padding: 16,
+ backgroundColor: Colors.LbryGreen,
+ marginLeft: 16,
+ marginTop: 16,
+ marginRight: 16
+ },
+ rewardDriverText: {
+ color: Colors.White,
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 14,
+ lineHeight: 16
}
});
diff --git a/src/main/java/io/lbry/browser/reactmodules/FirebaseModule.java b/src/main/java/io/lbry/browser/reactmodules/FirebaseModule.java
index 7804091d..55910f3f 100644
--- a/src/main/java/io/lbry/browser/reactmodules/FirebaseModule.java
+++ b/src/main/java/io/lbry/browser/reactmodules/FirebaseModule.java
@@ -55,6 +55,7 @@ public class FirebaseModule extends ReactContextBaseJavaModule {
@ReactMethod
public void logException(boolean fatal, String message, ReadableMap payload) {
Bundle bundle = new Bundle();
+ bundle.putString("message", message);
if (payload != null) {
HashMap payloadMap = payload.toHashMap();
for (Map.Entry entry : payloadMap.entrySet()) {