From 29fc14345414997209bd6979c3ffe5ca9b48b622 Mon Sep 17 00:00:00 2001 From: Omar Date: Sun, 30 Dec 2018 23:49:28 -0800 Subject: [PATCH] Twitter (Auth + Username) --- README.md | 10 +- android/app/build.gradle | 1 + .../main/java/com/tagfer/MainApplication.java | 2 + android/settings.gradle | 2 + ios/Tagfer.xcodeproj/project.pbxproj | 19 ++++ package-lock.json | 25 +++++ package.json | 1 + src/config/apiEndpoints.js | 8 +- src/config/router.js | 17 +++- src/screens/auth/SignupScreenTwo.js | 98 +++++++++++++++---- src/screens/auth/TwitterWebView.js | 32 ++++++ src/utils/errorHandler.js | 2 +- 12 files changed, 193 insertions(+), 24 deletions(-) create mode 100644 src/screens/auth/TwitterWebView.js diff --git a/README.md b/README.md index 82c4d33..0084969 100644 --- a/README.md +++ b/README.md @@ -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 ``` \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index fe5a6b1..c157b36 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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') diff --git a/android/app/src/main/java/com/tagfer/MainApplication.java b/android/app/src/main/java/com/tagfer/MainApplication.java index 457f448..a9221ba 100644 --- a/android/app/src/main/java/com/tagfer/MainApplication.java +++ b/android/app/src/main/java/com/tagfer/MainApplication.java @@ -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 getPackages() { return Arrays.asList( new MainReactPackage(), + new RNCWebViewPackage(), new VectorIconsPackage(), new RNGestureHandlerPackage(), new RealmReactPackage() diff --git a/android/settings.gradle b/android/settings.gradle index 24122d1..9d5e610 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -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' diff --git a/ios/Tagfer.xcodeproj/project.pbxproj b/ios/Tagfer.xcodeproj/project.pbxproj index fe88636..6dc2d45 100644 --- a/ios/Tagfer.xcodeproj/project.pbxproj +++ b/ios/Tagfer.xcodeproj/project.pbxproj @@ -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 = ""; 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 = ""; 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 = ""; 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 = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + D20792500D4B45FCBCC640FE /* libRNCWebView.a */ = {isa = PBXFileReference; name = "libRNCWebView.a"; path = "libRNCWebView.a"; sourceTree = ""; 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 = ""; @@ -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; diff --git a/package-lock.json b/package-lock.json index 9510bc2..dab0434 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index d53b316..fffc4fe 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/config/apiEndpoints.js b/src/config/apiEndpoints.js index de7ec0e..aab124d 100644 --- a/src/config/apiEndpoints.js +++ b/src/config/apiEndpoints.js @@ -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; diff --git a/src/config/router.js b/src/config/router.js index 543388f..c738b2f 100644 --- a/src/config/router.js +++ b/src/config/router.js @@ -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; diff --git a/src/screens/auth/SignupScreenTwo.js b/src/screens/auth/SignupScreenTwo.js index 2414612..39a4b70 100644 --- a/src/screens/auth/SignupScreenTwo.js +++ b/src/screens/auth/SignupScreenTwo.js @@ -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 ( +