Twitter (Auth + Username)

This commit is contained in:
Omar 2018-12-30 23:49:28 -08:00
parent f65886664d
commit 29fc143454
12 changed files with 193 additions and 24 deletions

View File

@ -4,10 +4,18 @@
To run the app locally please do the following:
```bash
git clone https://omihilmy@bitbucket.org/tagfer_team/tagfer-app.git
git clone https://bitbucket.org/tagfer_team/tagfer-app.git
cd tagfer-app
npm install
react-native link
react-native run-ios # For iOS
react-native run-andorid # For Android
```
Also, you need to have the `tagfer-server` running to process the app's requests.
```bash
git clone https://bitbucket.org/tagfer_team/tagfer-server.git
cd tagfer-server
npm install
node server.js
```

View File

@ -137,6 +137,7 @@ android {
}
dependencies {
compile project(':react-native-webview')
compile project(':react-native-vector-icons')
compile project(':react-native-gesture-handler')
compile project(':realm')

View File

@ -3,6 +3,7 @@ package com.tagfer;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import io.realm.react.RealmReactPackage;
@ -26,6 +27,7 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNCWebViewPackage(),
new VectorIconsPackage(),
new RNGestureHandlerPackage(),
new RealmReactPackage()

View File

@ -1,4 +1,6 @@
rootProject.name = 'Tagfer'
include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-gesture-handler'

View File

@ -53,6 +53,7 @@
D9DE5DDB1B7D4D3A97259D9B /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F025858B6C364BB985AFD241 /* Octicons.ttf */; };
E42DCC4F98F24BC99A74B29B /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 05246261280A4D30B02BFE85 /* SimpleLineIcons.ttf */; };
0446F832C7AD487A80B3E846 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4F118585274849369219BFCB /* Zocial.ttf */; };
AD3198E72354473C9CFBB5F9 /* libRNCWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D20792500D4B45FCBCC640FE /* libRNCWebView.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -377,6 +378,8 @@
F025858B6C364BB985AFD241 /* Octicons.ttf */ = {isa = PBXFileReference; name = "Octicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
05246261280A4D30B02BFE85 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; name = "SimpleLineIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
4F118585274849369219BFCB /* Zocial.ttf */ = {isa = PBXFileReference; name = "Zocial.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
A6904FF8C83444048D6F2AF5 /* RNCWebView.xcodeproj */ = {isa = PBXFileReference; name = "RNCWebView.xcodeproj"; path = "../node_modules/react-native-webview/ios/RNCWebView.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
D20792500D4B45FCBCC640FE /* libRNCWebView.a */ = {isa = PBXFileReference; name = "libRNCWebView.a"; path = "libRNCWebView.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -409,6 +412,7 @@
840B2F4BA84C4F819BD96330 /* libz.tbd in Frameworks */,
C82C9AD575F240A5AAC7BD27 /* libRNGestureHandler.a in Frameworks */,
054B5176D9D541119DF55198 /* libRNVectorIcons.a in Frameworks */,
AD3198E72354473C9CFBB5F9 /* libRNCWebView.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -600,6 +604,7 @@
5F8E09E191C84292BA09C78C /* RealmReact.xcodeproj */,
D85798D39A3B49938F477F4D /* RNGestureHandler.xcodeproj */,
2DACAA5CB9F345B5888D681E /* RNVectorIcons.xcodeproj */,
A6904FF8C83444048D6F2AF5 /* RNCWebView.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
@ -1261,12 +1266,14 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Debug;
@ -1291,12 +1298,14 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Release;
@ -1322,6 +1331,7 @@
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Debug;
@ -1346,6 +1356,7 @@
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Release;
@ -1378,12 +1389,14 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Debug;
@ -1416,12 +1429,14 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Release;
@ -1453,12 +1468,14 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Debug;
@ -1490,12 +1507,14 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/realm/src/**",
"$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-webview/ios",
);
};
name = Release;

25
package-lock.json generated
View File

@ -8596,6 +8596,31 @@
}
}
},
"react-native-webview": {
"version": "2.14.3",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-2.14.3.tgz",
"integrity": "sha512-hKG+3G1zLosbGFm/QgZsjBWDnf9tX4UGshcCzHBifXKgwhRY3sscs70lYdGMV/J7SxVRyiA4SebUiwUwbsstyw==",
"requires": {
"escape-string-regexp": "^1.0.5",
"fbjs": "^0.8.17"
},
"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"
}
}
}
},
"react-navigation": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/react-navigation/-/react-navigation-3.0.9.tgz",

View File

@ -15,6 +15,7 @@
"react-native-flash-message": "^0.1.10",
"react-native-gesture-handler": "^1.0.12",
"react-native-vector-icons": "^4.6.0",
"react-native-webview": "^2.14.3",
"react-navigation": "^3.0.9",
"realm": "^2.21.1"
},

View File

@ -1,4 +1,4 @@
const baseurl = 'http://localhost:3000';
const baseurl = 'http://127.0.0.1:3000';
const endpoints = {
login: {
method: 'POST',
@ -15,7 +15,11 @@ const endpoints = {
tagferIdExists: (tagferId) => ({
method: 'GET',
url: `${baseurl}/auth/tagferId/${tagferId}/exists`
})
}),
twitterToken: {
method: 'GET',
url: `${baseurl}/auth/twitter/token`
}
};
export default endpoints;

View File

@ -3,9 +3,10 @@ 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 TwitterWebView from '../screens/auth/TwitterWebView';
import ForgotPasswordScreen from '../screens/auth/ForgotPasswordScreen';
const StackNavigator = createStackNavigator({
const MainNavigator = createStackNavigator({
welcome: WelcomeScreen,
login: LoginScreen,
forgotPassword: ForgotPasswordScreen,
@ -13,6 +14,18 @@ const StackNavigator = createStackNavigator({
signupTwo: SignupScreenTwo
});
const AppNavigator = createAppContainer(StackNavigator);
const RootNavigator = createStackNavigator(
{
Main: {
screen: MainNavigator,
navigationOptions: { header: null }
},
twitterWebView: TwitterWebView
},
{
mode: 'modal'
});
const AppNavigator = createAppContainer(RootNavigator);
export default AppNavigator;

View File

@ -25,11 +25,15 @@ export default class SignupScreenTwo extends React.Component {
tagferId: '',
error: '',
loading: false,
twitterDisabled: false
twitterDisabled: false,
twitterUsername: false
};
this.keyboardDidHide = this.keyboardDidHide.bind(this);
this.onChangeText = this.onChangeText.bind(this);
this.openTwitterWebView = this.openTwitterWebView.bind(this);
this.setTwitterUsername = this.setTwitterUsername.bind(this);
this.removeTwitterUsername = this.removeTwitterUsername.bind(this);
}
componentDidMount() {
@ -40,9 +44,7 @@ export default class SignupScreenTwo extends React.Component {
this.keyboardDidHideListener.remove();
}
/**
* Poulates the state.error dynmically if there are errors as the user types.
*/
// Poulates the state.error dynmically if there are errors as the user types.
onChangeText(tagferId) {
let error = '';
if (tagferId.indexOf('@') !== -1) {
@ -83,21 +85,80 @@ export default class SignupScreenTwo extends React.Component {
});
}
/**
* Disables the twitter button to minimize distraction for UX.
*/
// Wrapper to set the tagferId to the twitter username
setTwitterUsername(username) {
this.input.blur();
this.setState({ tagferId: username, twitterUsername: true });
}
// Network request to our API to get an OAuth token
async getTwitterAuthURL() {
const twitter = { authenticate: 'https://api.twitter.com/oauth/authenticate' };
const result = await fetchAuth(endpoints.twitterToken);
if (result.error) { throw result.error; }
return `${twitter.authenticate}?oauth_token=${result.token}`;
}
// Navigates to a twitter webview modal, when the username is fetched successfully it will execute onSuccess
async openTwitterWebView() {
this.setState({ loading: true, error: '' });
try {
const url = await this.getTwitterAuthURL();
this.setState({ loading: false });
this.props.navigation.navigate('twitterWebView', { url, onSuccess: this.setTwitterUsername });
} catch (error) {
this.setState({ error, loading: false });
}
}
// Disables the twitter button to minimize distraction for UX
keyboardDidHide() {
this.setState({ twitterDisabled: false });
}
// Button is diabled if there is no tagferId or there is an error
isButtonDisabled() {
return isEmpty(this.state.tagferId) || !isEmpty(this.state.error);
}
// Disbaled if user is typing or we are during a request
isTwitterDisabled() {
return this.state.twitterDisabled || this.state.loading;
}
removeTwitterUsername() {
this.setState({ tagferId: '', twitterUsername: false });
}
renderTwitterButtons() {
if (this.state.twitterUsername) {
return (
<Button
onPress={this.removeTwitterUsername}
title={this.state.tagferId}
buttonStyle={styles.twitterUsernameButtonStyle}
fontSize={17}
textStyle={{ color: colors.headerBlue }}
disabled={this.isTwitterDisabled()}
icon={styles.twitterIconStyle('#1DA1F2')}
iconRight={{ name: 'close', color: colors.headerBlue }}
/>
);
}
return (
<Button
onPress={this.openTwitterWebView}
title={strings('userCreateFlow.createTagferId.twitterButton')}
buttonStyle={styles.twitterButtonStyle}
fontSize={15}
disabled={this.isTwitterDisabled()}
icon={styles.twitterIconStyle(colors.white)}
/>);
}
renderContent() {
return (
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: colors.white }} keyboardShouldPersistTaps="handled">
@ -119,6 +180,7 @@ export default class SignupScreenTwo extends React.Component {
autoCorrect={false}
autoCapitalize='none'
inputStyle={styles.inputTextStyle}
editable={!this.state.twitterUsername}
ref={ref => (this.input = ref)}
/>
@ -135,13 +197,7 @@ export default class SignupScreenTwo extends React.Component {
</View>
<View style={styles.twitterButtonContainerStyle}>
<Button
title={strings('userCreateFlow.createTagferId.twitterButton')}
buttonStyle={styles.twitterButtonStyle}
fontSize={15}
disabled={this.isTwitterDisabled()}
icon={styles.twitterIconStyle}
/>
{this.renderTwitterButtons()}
</View>
</View>
@ -225,12 +281,18 @@ const styles = {
borderRadius: 25,
backgroundColor: '#1DA1F2'
},
twitterIconStyle: {
twitterUsernameButtonStyle: {
alignSelf: 'flex-start',
height: 50,
borderRadius: 25,
backgroundColor: colors.offWhite
},
twitterIconStyle: (color) => ({
name: 'twitter',
type: 'font-awesome',
color: colors.white,
size: 25
},
size: 25,
color
}),
inputTextStyle: {
marginLeft: 15,
color: colors.black

View File

@ -0,0 +1,32 @@
import React from 'react';
import { Button } from 'react-native';
import { WebView } from 'react-native-webview';
export default class TwitterWebView extends React.Component {
static navigationOptions = ({ navigation }) => ({
headerLeft: null,
headerRight: <Button title="Done" onPress={() => navigation.goBack()} />
});
/**
* This the bridge connecting the WebView and ReactNative. The data you see here is sent by postMessage from
* within the DOM in the WebView.
*/
onComplete(data) {
const result = JSON.parse(data);
if (result.username) {
this.props.navigation.state.params.onSuccess(result.username);
}
this.props.navigation.goBack();
}
render() {
return (
<WebView
source={{ uri: this.props.navigation.state.params.url }}
onMessage={(event) => this.onComplete(event.nativeEvent.data)}
/>
);
}
}

View File

@ -69,7 +69,7 @@ export function showFlashErrorMessage(error) {
description: errorObject.desc,
type: getFlashMessageType(errorObject.type),
icon: 'auto',
duration: 3000
duration: 8000
});
}