Phone Verification

This commit is contained in:
Omar 2019-01-01 13:58:08 -08:00
parent 29fc143454
commit 7a95090359
11 changed files with 456 additions and 14 deletions

99
package-lock.json generated
View File

@ -4647,6 +4647,11 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"fuse.js": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-2.6.2.tgz",
"integrity": "sha1-1dmU/alvVDtaUd84tyzsnMYNneo="
},
"gauge": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz",
@ -6963,6 +6968,11 @@
"resolved": "https://registry.npmjs.org/lodash.times/-/lodash.times-4.3.2.tgz",
"integrity": "sha1-Ph8lZcQxdU1Uq1fy7RdBk5KFyh0="
},
"lodash.toarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -7468,6 +7478,11 @@
}
}
},
"moment": {
"version": "2.19.3",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz",
"integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8="
},
"morgan": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
@ -7581,6 +7596,14 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true
},
"node-emoji": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz",
"integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==",
"requires": {
"lodash.toarray": "^4.4.0"
}
},
"node-fetch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz",
@ -8500,6 +8523,63 @@
"yargs": "^9.0.0"
}
},
"react-native-country-picker-modal": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/react-native-country-picker-modal/-/react-native-country-picker-modal-0.6.2.tgz",
"integrity": "sha1-upcRi+Q3O+DBHNUeRF5r1Eji8co=",
"requires": {
"fuse.js": "2.6.2",
"lodash": "4.12.0",
"node-emoji": "1.8.1",
"prop-types": "15.6.0",
"react-native-safe-area-view": "^0.7.0",
"world-countries": "1.8.0"
},
"dependencies": {
"fbjs": {
"version": "0.8.17",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
"requires": {
"core-js": "^1.0.0",
"isomorphic-fetch": "^2.1.1",
"loose-envify": "^1.0.0",
"object-assign": "^4.1.0",
"promise": "^7.1.1",
"setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.18"
}
},
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
},
"lodash": {
"version": "4.12.0",
"resolved": "http://registry.npmjs.org/lodash/-/lodash-4.12.0.tgz",
"integrity": "sha1-K9bcRqBA9Z5obJcu0h2T3FkFMlg="
},
"prop-types": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
"requires": {
"fbjs": "^0.8.16",
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
}
},
"react-native-safe-area-view": {
"version": "0.7.0",
"resolved": "http://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.7.0.tgz",
"integrity": "sha512-SjLdW/Th0WVMhyngH4O6yC21S+O4U4AAG3QxBr7fZ2ftgjXSpKbDHAhEpxBdFwei6HsnsC2h9oYMtPpaW9nfGg==",
"requires": {
"hoist-non-react-statics": "^2.3.1"
}
}
}
},
"react-native-elements": {
"version": "0.19.1",
"resolved": "http://registry.npmjs.org/react-native-elements/-/react-native-elements-0.19.1.tgz",
@ -8536,6 +8616,15 @@
}
}
},
"react-native-masked-text": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/react-native-masked-text/-/react-native-masked-text-1.9.2.tgz",
"integrity": "sha512-+EctPpbQkXJe3ljWggkMRv4Fn+Oy7WKegFPKzK/N91qV9aZWZZwMosWDiH4u3aU+cO+GAjWAZn9ilSLGDroJrQ==",
"requires": {
"moment": "2.19.3",
"tinymask": "^1.0.2"
}
},
"react-native-safe-area-view": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.11.0.tgz",
@ -10294,6 +10383,11 @@
"resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
"integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM="
},
"tinymask": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinymask/-/tinymask-1.0.2.tgz",
"integrity": "sha1-3zd1qUCHsNPQVsJW6JI8AbfsCUE="
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@ -10774,6 +10868,11 @@
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"world-countries": {
"version": "1.8.0",
"resolved": "http://registry.npmjs.org/world-countries/-/world-countries-1.8.0.tgz",
"integrity": "sha1-F/SOfoRwrFohNq1pON/GVvwry5U="
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",

View File

@ -11,9 +11,11 @@
"i18n-js": "^3.1.0",
"react": "16.6.3",
"react-native": "0.57.8",
"react-native-country-picker-modal": "^0.6.2",
"react-native-elements": "^0.19.1",
"react-native-flash-message": "^0.1.10",
"react-native-gesture-handler": "^1.0.12",
"react-native-masked-text": "^1.9.2",
"react-native-vector-icons": "^4.6.0",
"react-native-webview": "^2.14.3",
"react-navigation": "^3.0.9",

View File

@ -19,6 +19,14 @@ const endpoints = {
twitterToken: {
method: 'GET',
url: `${baseurl}/auth/twitter/token`
},
sendCode: {
method: 'POST',
url: `${baseurl}/auth/phone/code`
},
verifyCode: {
method: 'POST',
url: `${baseurl}/auth/phone/verify`
}
};

View File

@ -1,17 +1,21 @@
import { createStackNavigator, createAppContainer } from 'react-navigation';
import WelcomeScreen from '../screens/onboaring/WelcomeScreen';
import LoginScreen from '../screens/auth/LoginScreen';
import SignupScreenOne from '../screens/auth/SignupScreenOne';
import SignupScreenTwo from '../screens/auth/SignupScreenTwo';
import SignupScreenOne from '../screens/auth/SignupScreen1';
import SignupScreenTwo from '../screens/auth/SignupScreen2';
import SignupScreenThreeA from '../screens/auth/SignupScreen3a';
import SignupScreenThreeB from '../screens/auth/SignupScreen3b';
import TwitterWebView from '../screens/auth/TwitterWebView';
import ForgotPasswordScreen from '../screens/auth/ForgotPasswordScreen';
const MainNavigator = createStackNavigator({
const MainNavigator = createStackNavigator({
welcome: WelcomeScreen,
login: LoginScreen,
forgotPassword: ForgotPasswordScreen,
signupOne: SignupScreenOne,
signupTwo: SignupScreenTwo
signup1: SignupScreenOne,
signup2: SignupScreenTwo,
signup3a: SignupScreenThreeA,
signup3b: SignupScreenThreeB,
});
const RootNavigator = createStackNavigator(

View File

@ -553,13 +553,13 @@
},
"phoneAdd": {
"countrySelect": "Click the flag to select your country",
"description": "Please confirm your country code and enter your phone number to activate your Tagfer account. We will send you an SMS message for verification",
"description": "Please confirm your country code and enter your phone number to activate your Tagfer account. We will send you an SMS message for verification.",
"rateDisclaimer": "Standard messaging and data rates apply",
"title": "Add Phone Number"
"title": "Phone Number"
},
"phoneVerify": {
"description": "Verify your phone and activate your Tagfer account. Enter the code that was sent to: {{phoneNumber}}.",
"title": "Verify SMS Code"
"description": "Verify your phone and activate your Tagfer account. Enter the code that was sent to: {{phoneNumber}}",
"title": "Phone Verification"
},
"signUp": {
"minPassword": "6+ characters",

View File

@ -133,7 +133,7 @@ export default class LoginScreen extends React.Component {
<TextButton
leftText={strings('userLoginScreen.userCreateText')}
rightText={strings('userLoginScreen.userCreateButton')}
onPress={() => this.props.navigation.navigate('signupOne')}
onPress={() => this.props.navigation.navigate('signup1')}
disabled={this.state.loading}
/>
</View>

View File

@ -49,7 +49,7 @@ export default class SignupScreenOne extends React.Component {
const errorObject = getErrorObject(errors.AUTH_EMAIL_ALREADY_EXISTS);
this.setState({ error: errorObject.desc });
} else {
this.props.navigation.navigate('signupTwo');
this.props.navigation.navigate('signup2');
}
}).catch(() => {
this.setState({ loading: false });

View File

@ -16,7 +16,8 @@ export default class SignupScreenTwo extends React.Component {
static navigationOptions = {
title: strings('userCreateFlow.createTagferId.title'),
headerStyle: { borderBottomWidth: 0 },
headerTintColor: '#0D497E'
headerTintColor: '#0D497E',
headerRight: <Text style={{ color: colors.middleGrey, marginRight: 5 }}>2/6</Text>
};
constructor(props) {
@ -77,7 +78,7 @@ export default class SignupScreenTwo extends React.Component {
const errorObject = getErrorObject(errors.AUTH_UID_ALREADY_EXISTS);
this.setState({ error: errorObject.desc });
} else {
this.props.navigation.navigate('signupThree');
this.props.navigation.navigate('signup3a');
}
}).catch(() => {
this.setState({ loading: false });

View File

@ -0,0 +1,166 @@
import React from 'react';
import { Dimensions, KeyboardAvoidingView, Platform, ScrollView, Text, View } from 'react-native';
import { Button } from 'react-native-elements';
import { TextInputMask } from 'react-native-masked-text';
import CountryPicker from 'react-native-country-picker-modal';
import { strings } from '../../locales/i18n';
import { showFlashErrorMessage } from '../../utils/errorHandler';
import { fetchAuth } from '../../utils/fetch';
import colors from '../../config/colors.json';
import errors from '../../config/errors';
import endpoints from '../../config/apiEndpoints';
export default class SignupScreenThreeA extends React.Component {
static navigationOptions = {
title: strings('userCreateFlow.phoneAdd.title'),
headerStyle: { borderBottomWidth: 0 },
headerTintColor: '#0D497E',
headerRight: <Text style={{ color: colors.middleGrey, marginRight: 5 }}>3/6</Text>
};
constructor(props) {
super(props);
this.state = {
phoneNumber: '',
country: {
cca2: 'US',
callingCode: '1'
}
};
}
// Reformat the masked number and send the request
onSaveButtonPress() {
this.setState({ loading: true });
const phoneNumber = `+${this.state.country.callingCode}${this.input.getRawValue()}`;
fetchAuth(endpoints.sendCode, { phoneNumber }).then(res => {
if (res.error) {
showFlashErrorMessage(res.error);
} else {
this.props.navigation.navigate('signup3b', { phoneNumber });
}
}).catch(() => showFlashErrorMessage(errors.APP_NETWORK_ERROR))
.finally(() => this.setState({ loading: false }));
}
// 14 chars include mask |(999) 999-9999|, its actually 10
isDisabled() {
return this.state.phoneNumber.length < 14;
}
renderContent() {
return (
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: colors.white }} keyboardShouldPersistTaps="handled">
<View style={{ flex: 1 }}>
{/* INSTRUCTIONS */}
<Text style={styles.instructions}>{strings('userCreateFlow.phoneAdd.description')}</Text>
<View style={{ flexDirection: 'row', marginLeft: 20 }}>
{/* COUNTRY FLAG */}
<CountryPicker
closeable
filterable
onChange={(country) => this.setState({ country })}
onClose={() => this.props.navigation.goBack()}
cca2={this.state.country.cca2}
translation='eng'
styles={{ itemCountryName: { borderBottomWidth: 0 } }}
/>
{/* CALLING CODE */}
<Text style={styles.callingCodeText}>+{this.state.country.callingCode}</Text>
{/* PHONE NUMBER */}
<TextInputMask
ref={ref => (this.input = ref)}
value={this.state.phoneNumber}
underlineColorAndroid={'transparent'}
autoCapitalize={'none'}
autoCorrect={false}
onChangeText={(phoneNumber) => this.setState({ phoneNumber })}
placeholder={'Phone Number'}
keyboardType={Platform.OS === 'ios' ? 'number-pad' : 'numeric'}
style={styles.textInput}
type={'cel-phone'}
options={{ withDDD: true, dddMask: '(999) 999-9999' }}
autoFocus
/>
</View>
</View>
{/* SAVE AND CONTINUE BUTTON */}
<Button
onPress={this.onSaveButtonPress.bind(this)}
title={strings('common.buttons.saveAndContinue')}
buttonStyle={styles.buttonStyle}
disabled={this.isDisabled()}
loading={this.state.loading}
fontSize={20}
iconRight={styles.chevronIconStyle}
/>
</ScrollView>
);
}
render() {
if (Platform.OS === 'android') {
return this.renderContent();
}
return (
<KeyboardAvoidingView style={{ flex: 1 }} behavior="padding">
{this.renderContent()}
</KeyboardAvoidingView>
);
}
}
const SCREEN_WIDTH = Dimensions.get('window').width;
const styles = {
instructions: {
left: 20,
width: '90%',
marginTop: 20,
marginBottom: 30,
fontSize: 16,
color: colors.grey,
textAlign: 'justify'
},
countryPicker: {
alignItems: 'center',
justifyContent: 'center'
},
callingCodeView: {
alignItems: 'center',
justifyContent: 'center'
},
callingCodeText: {
fontSize: 20,
paddingRight: 10,
paddingLeft: 10
},
textInput: {
padding: 0,
margin: 0,
flex: 1,
fontSize: 20
},
buttonStyle: {
width: SCREEN_WIDTH * 0.9,
marginTop: 15,
marginBottom: 30,
borderRadius: 5,
backgroundColor: colors.buttonBlue
},
chevronIconStyle: {
name: 'chevron-right',
color: colors.white,
size: 30,
style: { marginLeft: 0 }
}
};

View File

@ -0,0 +1,162 @@
import React from 'react';
import { Dimensions, KeyboardAvoidingView, Platform, ScrollView, Text, View } from 'react-native';
import { Button } from 'react-native-elements';
import { TextInputMask } from 'react-native-masked-text';
import { strings } from '../../locales/i18n';
import { showFlashErrorMessage } from '../../utils/errorHandler';
import { fetchAuth } from '../../utils/fetch';
import colors from '../../config/colors.json';
import errors from '../../config/errors';
import endpoints from '../../config/apiEndpoints';
export default class SignupScreenThreeB extends React.Component {
static navigationOptions = {
title: strings('userCreateFlow.phoneVerify.title'),
headerStyle: { borderBottomWidth: 0 },
headerTintColor: '#0D497E',
headerRight: <Text style={{ color: colors.middleGrey, marginRight: 5 }}>3/6</Text>
};
constructor(props) {
super(props);
this.state = {
code: ''
};
}
// Reformat the masked code and send the request
onSaveButtonPress() {
this.setState({ loading: true });
const phoneNumber = this.props.navigation.state.params.phoneNumber;
const code = this.input.getRawValue();
fetchAuth(endpoints.verifyCode, { phoneNumber, code }).then(res => {
console.log(res);
if (res.error) {
showFlashErrorMessage(res.error);
} else if (res.result === false) {
showFlashErrorMessage(errors.AUTH_VERIFICATION_CODE_MISMATCH);
} else {
this.props.navigation.navigate('signup4');
}
}).catch(() => showFlashErrorMessage(errors.APP_NETWORK_ERROR))
.finally(() => this.setState({ loading: false }));
}
isDisabled() {
return this.state.code.length !== 7;
}
renderContent() {
const phoneNumber = '3014376413' || this.props.navigation.state.params.phoneNumber;
return (
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: colors.white }} keyboardShouldPersistTaps="handled">
<View style={{ flex: 1 }}>
{/* INSTRUCTIONS */}
<Text style={styles.instructions}>{strings('userCreateFlow.phoneVerify.description', { phoneNumber })}</Text>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
{/* CODE */}
<TextInputMask
ref={ref => (this.input = ref)}
value={this.state.code}
underlineColorAndroid={'transparent'}
autoCapitalize={'none'}
autoCorrect={false}
onChangeText={(code) => this.setState({ code })}
placeholder={'_ _ _ _ _ _'}
keyboardType={Platform.OS === 'ios' ? 'number-pad' : 'numeric'}
style={styles.textInput}
type={'cel-phone'}
options={{ withDDD: true, dddMask: '999 999' }}
autoFocus
maxLength={7}
/>
</View>
<Text style={styles.wrongNumberButtonStyle} onPress={() => this.props.navigation.goBack()}>Wrong Number?</Text>
</View>
{/* SAVE AND CONTINUE BUTTON */}
<Button
onPress={this.onSaveButtonPress.bind(this)}
title={strings('common.buttons.saveAndContinue')}
buttonStyle={styles.buttonStyle}
disabled={this.isDisabled()}
loading={this.state.loading}
fontSize={20}
iconRight={styles.chevronIconStyle}
/>
</ScrollView>
);
}
render() {
if (Platform.OS === 'android') {
return this.renderContent();
}
return (
<KeyboardAvoidingView style={{ flex: 1 }} behavior="padding">
{this.renderContent()}
</KeyboardAvoidingView>
);
}
}
const SCREEN_WIDTH = Dimensions.get('window').width;
const styles = {
instructions: {
left: 20,
width: '90%',
marginTop: 20,
marginBottom: 30,
fontSize: 16,
color: colors.grey,
textAlign: 'justify'
},
countryPicker: {
alignItems: 'center',
justifyContent: 'center'
},
callingCodeView: {
alignItems: 'center',
justifyContent: 'center'
},
callingCodeText: {
fontSize: 20,
paddingRight: 10,
paddingLeft: 10
},
textInput: {
margin: 0,
fontSize: 38
},
buttonStyle: {
width: SCREEN_WIDTH * 0.9,
marginTop: 15,
marginBottom: 30,
borderRadius: 5,
backgroundColor: colors.buttonBlue
},
chevronIconStyle: {
name: 'chevron-right',
color: colors.white,
size: 30,
style: { marginLeft: 0 }
},
wrongNumberButtonStyle: {
color: colors.darkBlue,
fontSize: 15,
fontWeight: 'bold',
marginLeft: 20,
marginTop: 20
}
};

View File

@ -34,7 +34,7 @@ export function getErrorObject(error) {
};
case errors.AUTH_PHONE_NOT_CACHED: return {
type: 'Authentication Error',
desc: 'Please go back and make another verification code request'
desc: 'Phone number is not recognized.'
};
case errors.AUTH_VERIFICATION_CODE_MISMATCH: return {
type: 'Authentication Error',