additional rewards page changes and new verification flow

This commit is contained in:
Akinwale Ariwodola 2019-05-09 18:52:19 +01:00
parent f1393ae707
commit 2c9c822784
11 changed files with 529 additions and 6 deletions

View file

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

View file

@ -0,0 +1,17 @@
import { connect } from 'react-redux';
import { doToast } from 'lbry-redux';
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))
});
export default connect(select, perform)(RewardEnrolment);

View file

@ -0,0 +1,51 @@
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 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 } = this.props;
navigation.navigate({ routeName: 'DiscoverStack' })
}
onEnrollPressed = () => {
const { navigation } = this.props;
navigation.navigate({ routeName: 'Verification' })
}
render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
return (
<View style={rewardStyle.enrollContainer} onPress>
<View style={rewardStyle.summaryRow}>
<Icon name="award" size={36} color={Colors.White} />
<Text style={rewardStyle.summaryText}>
{unclaimedRewardAmount} unclaimed credits
</Text>
</View>
<View style={rewardStyle.onboarding}>
<Text style={rewardStyle.enrollDescText}>LBRY credits allow you to purchase content, publish content, and influence the network. You can start earning credits by watching videos on LBRY.</Text>
</View>
<View style={rewardStyle.buttonRow}>
<Link style={rewardStyle.notInterestedLink} text={"Not interested"} onPress={this.onNotInterestedPressed} />
<Button style={rewardStyle.enrollButton} theme={"light"} text={"Enroll"} onPress={this.onEnrollPressed} />
</View>
</View>
);
}
}
export default RewardEnrolment;

View file

@ -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",

View file

@ -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';
@ -153,7 +154,10 @@ class RewardsPage extends React.PureComponent {
return (
<View style={rewardStyle.container}>
<UriBar navigation={navigation} />
<ScrollView
{!this.state.isRewardApproved && <RewardEnrolment navigation={navigation} />}
{this.state.isRewardApproved && <ScrollView
ref={ref => this.scrollView = ref}
keyboardShouldPersistTaps={'handled'}
style={rewardStyle.scrollContainer}
@ -162,7 +166,7 @@ class RewardsPage extends React.PureComponent {
{this.state.revealVerification && this.renderVerification()}
{this.renderUnclaimedRewards()}
{this.renderClaimedRewards()}
</ScrollView>
</ScrollView>}
</View>
);
}

View file

@ -0,0 +1,26 @@
import { connect } from 'react-redux';
import { doToast } from 'lbry-redux';
import {
doUserEmailNew,
doUserEmailToVerify,
selectEmailNewErrorMessage,
selectEmailNewIsPending,
selectEmailToVerify,
doUserResendVerificationEmail,
} from 'lbryinc';
import Verification from './view';
const select = (state) => ({
emailToVerify: selectEmailToVerify(state),
emailNewErrorMessage: selectEmailNewErrorMessage(state),
emailNewPending: selectEmailNewIsPending(state),
});
const perform = dispatch => ({
addUserEmail: email => dispatch(doUserEmailNew(email)),
notify: data => dispatch(doToast(data)),
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email))
});
export default connect(select, perform)(Verification);

View file

@ -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 } = 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 (
<View style={firstRunStyle.container}>
<Text style={rewardStyle.verificationTitle}>{(Constants.PHASE_COLLECTION === this.state.phase) ? 'Email' : 'Verify Email'}</Text>
{(Constants.PHASE_COLLECTION === this.state.phase) &&
<View>
<Text style={firstRunStyle.paragraph}>Please provide an email address.</Text>
<TextInput style={firstRunStyle.emailInput}
placeholder={this.state.placeholder}
underlineColorAndroid="transparent"
value={this.state.email}
onChangeText={text => 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' });
}
}}
/>
<View style={rewardStyle.buttonContainer}>
{!this.state.verifyStarted &&
<Button
style={rewardStyle.verificationButton}
theme={"light"}
text={"Send verification email"}
onPress={this.onSendVerificationPressed} />}
{this.state.verifyStarted && emailNewPending &&
<ActivityIndicator size={"small"} color={Colors.White} style={rewardStyle.loading} />}
</View>
</View>}
{(Constants.PHASE_VERIFICATION === this.state.phase) &&
<View>
<Text style={firstRunStyle.paragraph}>An email has been sent to {this.state.email}.</Text>
<View style={rewardStyle.buttonContainer}>
<Button style={rewardStyle.verificationButton} theme={"light"} text={"Resend"} onPress={this.onResendPressed} />
<Link style={rewardStyle.verificationLink} text={"Edit"} onPress={this.onEditPressed} />
</View>
</View>
}
</View>
);
}
}
export default EmailVerifyPage;

View file

@ -0,0 +1,206 @@
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 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
};
componentDidMount() {
const { navigation } = this.props;
this.setState({ currentPage: 'emailVerify' });
}
componentWillReceiveProps(nextProps) {
const { emailNewErrorMessage, emailNewPending } = nextProps;
const { notify } = this.props;
/*if (this.state.emailSubmitted && !emailNewPending) {
this.setState({ emailSubmitted: false });
if (emailNewErrorMessage) {
notify ({ message: String(emailNewErrorMessage), isError: true });
} else {
// Request successful. Navigate to next page (wallet).
this.showNextPage();
}
}*/
}
handleLeftButtonPressed = () => {
/*// Go to setup account page when "Setup account" is pressed
if (Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage) {
return this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_COLLECT);
}
// Go to skip account page when "No, thanks" is pressed
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) {
this.showPage(Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT);
}*/
}
handleContinuePressed = () => {
/*const { notify } = this.props;
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
if (Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage) {
if (!this.state.walletPassword || this.state.walletPassword.trim().length < 6) {
return notify({ message: 'Your wallet password should be at least 6 characters long' });
}
this.closeFinalPage();
return;
}
if (Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage && !this.state.skipAccountConfirmed) {
notify({ message: 'Please confirm that you want to use LBRY without creating an account.' });
return;
}
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT !== this.state.currentPage && pageIndex === (FirstRunScreen.pages.length - 1)) {
this.closeFinalPage();
} else {
// TODO: Actions and page verification for specific pages
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) {
// handle email collect
this.handleEmailCollectPageContinue();
} else {
this.showNextPage();
}
}*/
}
handleEmailCollectPageContinue() {
/*const { notify, addUserEmail } = this.props;
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
// validate the email
if (!email || email.indexOf('@') === -1) {
return notify({
message: 'Please provide a valid email address to continue.',
});
}
addUserEmail(email);
this.setState({ emailSubmitted: true });
});*/
}
showNextPage() {
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
const nextPage = FirstRunScreen.pages[pageIndex + 1];
this.setState({ currentPage: nextPage });
if (nextPage === 'email-collect') {
// do not show the buttons (because we're waiting to get things ready)
this.setState({ showBottomContainer: false });
}
}
showPage(pageName) {
const pageIndex = FirstRunScreen.pages.indexOf(pageName);
if (pageIndex > -1) {
this.setState({ currentPage: pageName });
}
}
closeFinalPage() {
// Final page. Let the app know that first run experience is completed.
if (NativeModules.FirstRun) {
NativeModules.FirstRun.firstRunCompleted();
}
// Navigate to the splash screen
this.launchSplashScreen();
}
onEmailChanged = (email) => {
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT == this.state.currentPage) {
this.setState({ showSkip: (!email || email.trim().length === 0) });
} else {
this.setState({ showSkip: false });
}
}
onEmailViewLayout = () => {
this.setState({ showBottomContainer: true });
AsyncStorage.getItem('firstRunEmail').then(email => {
this.setState({ showSkip: !email || email.trim().length === 0 });
});
}
onCloseButtonPressed = () => {
const { navigation } = this.props;
navigation.goBack();
}
onWalletPasswordChanged = (password) => {
this.setState({ walletPassword: password });
}
onWalletViewLayout = () => {
this.setState({ showBottomContainer: true });
}
onSkipSwitchChanged = (checked) => {
this.setState({ skipAccountConfirmed: checked });
}
render() {
const {
addUserEmail,
emailNewErrorMessage,
emailNewPending,
emailToVerify,
notify,
resendVerificationEmail
} = this.props;
let page = null;
switch (this.state.currentPage) {
case 'emailVerify':
page = (
<EmailVerifyPage
addUserEmail={addUserEmail}
emailNewErrorMessage={emailNewErrorMessage}
emailNewPending={emailNewPending}
emailToVerify={emailToVerify}
notify={notify}
resendVerificationEmail={resendVerificationEmail}
/>
);
break;
}
return (
<View style={firstRunStyle.screenContainer}>
{page}
<TouchableOpacity style={firstRunStyle.closeButton} onPress={this.onCloseButtonPressed}>
<Text style={firstRunStyle.closeButtonText}>x</Text>
</TouchableOpacity>
</View>
);
}
}
export default VerificationScreen;

View file

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

View file

@ -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
},
@ -202,12 +220,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',
@ -222,6 +252,32 @@ const rewardStyle = StyleSheet.create({
},
failureFootnote: {
marginTop: 12
},
buttonContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
paddingLeft: 32,
paddingRight: 32,
marginTop: 24
},
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
}
});

View file

@ -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<String, Object> payloadMap = payload.toHashMap();
for (Map.Entry<String, Object> entry : payloadMap.entrySet()) {