├── .env.local_dev ├── .env.production ├── .env.staging ├── .gitignore ├── .gitmodules ├── AUTHORS.md ├── LICENSE ├── README.md ├── android ├── app │ ├── BUCK │ ├── app.iml │ ├── build.gradle │ ├── fabric.properties │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── seedstarsbase │ │ │ ├── MainActivity.java │ │ │ ├── MainApplication.java │ │ │ └── ReactNativeFabricLogger.java │ │ └── res │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystores │ ├── BUCK │ └── debug.keystore.properties └── settings.gradle ├── index.android.js ├── js ├── App.js ├── actions │ ├── auth.js │ ├── forms.js │ └── modals.js ├── components │ ├── BottomButton │ │ ├── bottomButton.js │ │ └── styles.js │ ├── LoadingModal │ │ ├── index.js │ │ └── styles.js │ ├── PaymentModal │ │ ├── index.js │ │ └── styles.js │ ├── WarningModal │ │ ├── index.js │ │ └── styles.js │ ├── facebookButton │ │ ├── facebookButton.js │ │ └── styles.js │ ├── signUpButton │ │ ├── SignUpButton.js │ │ └── styles.js │ └── spiner │ │ ├── index.js │ │ └── styles.js ├── constants │ └── index.js ├── containers │ ├── ApplicationTabs │ │ ├── Activity2 │ │ │ ├── index.js │ │ │ └── styles.js │ │ ├── CTA │ │ │ ├── index.js │ │ │ └── styles.js │ │ ├── Home │ │ │ ├── Tabs │ │ │ │ ├── Tab1 │ │ │ │ │ ├── index.js │ │ │ │ │ └── styles.js │ │ │ │ ├── Tab2 │ │ │ │ │ ├── index.js │ │ │ │ │ └── styles.js │ │ │ │ ├── Tab3 │ │ │ │ │ ├── index.js │ │ │ │ │ └── styles.js │ │ │ │ ├── index.js │ │ │ │ └── styles.js │ │ │ └── index.js │ │ ├── OrderDone │ │ │ ├── index.js │ │ │ └── styles.js │ │ ├── index.js │ │ └── styles.js │ ├── Login │ │ ├── index.js │ │ └── styles.js │ ├── LoginEmail │ │ ├── index.js │ │ └── styles.js │ ├── MainNavigation │ │ └── index.js │ ├── Register │ │ ├── index.js │ │ └── styles.js │ └── Splash │ │ ├── index.js │ │ └── styles.js ├── images │ ├── icon.png │ └── no_avatar.png ├── middlewares │ ├── Crashlytics.js │ └── index.js ├── reducers │ ├── applicationTabs.js │ ├── auth.js │ ├── forms.js │ ├── globalNavigator.js │ ├── index.js │ └── modals.js ├── store │ └── index.js ├── styles.js ├── styles │ ├── base.js │ ├── colors.js │ ├── form-white.js │ └── form.js └── utils │ ├── AnalyticsUtils.js │ ├── config.js │ ├── index.js │ ├── transformers.js │ └── validators.js ├── package.json └── shippable.yml /.env.local_dev: -------------------------------------------------------------------------------- 1 | BACKEND_URL= 2 | ANALYTICS_KEY= 3 | ONESIGNAL_ID= 4 | GOOGLE_PROJECT_NUMBER= 5 | BRANCH_KEY= 6 | CODE_PUSH_PRODUCTION_KEY= 7 | CODE_PUSH_STAGING_KEY= 8 | FACEBOOK_APP_ID= 9 | APP_VERSION_NAME= 10 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | BACKEND_URL= 2 | ANALYTICS_KEY= 3 | ONESIGNAL_ID= 4 | GOOGLE_PROJECT_NUMBER= 5 | BRANCH_KEY= 6 | CODE_PUSH_PRODUCTION_KEY= 7 | CODE_PUSH_STAGING_KEY= 8 | FACEBOOK_APP_ID= 9 | APP_VERSION_NAME= 10 | -------------------------------------------------------------------------------- /.env.staging: -------------------------------------------------------------------------------- 1 | BACKEND_URL= 2 | ANALYTICS_KEY= 3 | ONESIGNAL_ID= 4 | GOOGLE_PROJECT_NUMBER= 5 | BRANCH_KEY= 6 | CODE_PUSH_PRODUCTION_KEY= 7 | CODE_PUSH_STAGING_KEY= 8 | FACEBOOK_APP_ID= 9 | APP_VERSION_NAME= 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | .project 3 | .pydevproject 4 | .idea 5 | .vscode 6 | .settings 7 | 8 | # Random files 9 | *~ 10 | \#* 11 | 12 | # OSX 13 | # 14 | .DS_Store 15 | 16 | # Xcode 17 | # 18 | build/ 19 | *.pbxuser 20 | !default.pbxuser 21 | *.mode1v3 22 | !default.mode1v3 23 | *.mode2v3 24 | !default.mode2v3 25 | *.perspectivev3 26 | !default.perspectivev3 27 | xcuserdata 28 | *.xccheckout 29 | *.moved-aside 30 | DerivedData 31 | *.hmap 32 | *.ipa 33 | *.xcuserstate 34 | project.xcworkspace 35 | 36 | # Android/IJ 37 | # 38 | .idea 39 | .gradle 40 | local.properties 41 | 42 | # node.js 43 | # 44 | node_modules/ 45 | npm-debug.log 46 | 47 | # BUCK 48 | buck-out/ 49 | \.buckd/ 50 | android/app/libs 51 | android/keystores/debug.keystore 52 | android/android.iml 53 | 54 | # Scripts 55 | scripts/ 56 | .eslintrc 57 | .sass-lint.yml 58 | .prospector.yml 59 | .coveragerc 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scripts"] 2 | path = scripts 3 | url = https://github.com/Seedstars/culture-scripts 4 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | This code was created at Seedstars Labs (www.seedstarslabs.com) for internal use. 2 | 3 | - Daniel Silva 4 | - Fernando Gomes 5 | - Filipe Garcia 6 | - Jacinto Rodrigues 7 | - Luis Rodrigues 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Seedstars 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mobile App Base Project (Frontend) 2 | 3 | This repository includes a boilerplate project used for all Seedstars Labs mobile applications. It uses Django as backend and React as frontend. 4 | 5 | To test the application you need to also run this backend: https://github.com/seedstars/reactnative-backend-base 6 | 7 | We build on the shoulders of giants with the following technologies: 8 | 9 | * [React](https://github.com/facebook/react) 10 | * [React Native](https://github.com/facebook/react-native) 11 | * [Redux](https://github.com/reactjs/redux) Predictable state container for JavaScript apps 12 | * [Redux Dev Tools](https://github.com/gaearon/redux-devtools) DevTools for Redux with hot reloading, action replay, and customizable UI. Watch [Dan Abramov's talk](https://www.youtube.com/watch?v=xsSnOQynTHs) 13 | * [Redux Thunk](https://github.com/gaearon/redux-thunk) Thunk middleware for Redux - used in async actions 14 | * [Babel](http://babeljs.io) for ES6 and ES7 magic 15 | * [React Native Config](https://github.com/luggit/react-native-config) Bring some 12 factor love to your mobile apps! 16 | * [React Native Navigation Redux Helpers](https://github.com/bakery/react-native-navigation-redux-helpers) Redux actions and reducers for React Native experimental navigation 17 | * [React Native Tab View](https://github.com/react-native-community/react-native-tab-view) A cross-platform Tab View component for React Native 18 | * [React Native Vector Icons](https://github.com/oblador/react-native-vector-icons) 3000 Customizable Icons for React Native with support for NavBar/TabBar/ToolbarAndroid, image source and full stying. 19 | * [tcomb form](https://github.com/gcanti/tcomb-form) Forms library for react and [tcomb form native](https://github.com/gcanti/tcomb-form-native) for React Native 20 | * [react-native-communications](https://github.com/anarchicknight/react-native-communications) Open a web address or call, email, text or iMessage (iOS only) someone in React Native 21 | 22 | * [React Native Module for CodePush](https://github.com/Microsoft/react-native-code-push) React Native module for CodePush http://codepush.tools 23 | 24 | * [Branch Metrics React Native SDK Reference](https://github.com/BranchMetrics/react-native-branch-deep-linking) 25 | * [GoogleAnalyticsBridge](https://github.com/idehub/react-native-google-analytics-bridge) 26 | * [React Native FBSDK](https://github.com/facebook/react-native-fbsdk) 27 | * [react-native-fabric](https://github.com/corymsmith/react-native-fabric) A React Native library for Fabric, Crashlytics and Answers 28 | * [React Native OneSignal](https://github.com/geektimecoil/react-native-onesignal) React Native Push Notifications support with OneSignal integration. 29 | 30 | * [ESLint](http://eslint.org), [Airbnb Javascript/React Styleguide](https://github.com/airbnb/javascript), [Airbnb CSS / Sass Styleguide](https://github.com/airbnb/css) to maintain a consistent code style and [eslint-plugin-import](https://github.com/benmosher/eslint-plugin-import) to make sure all imports are correct 31 | 32 | 33 | 34 | ## Readme Notes 35 | 36 | * Command line starts with $, the command should run with user privileges 37 | * Command line starts with #, the command should run with root privileges 38 | 39 | 40 | ## Retrieve code 41 | 42 | * `$ git clone https://github.com/seedstars/reactnative-mobile-app-base.git` 43 | * `$ cd reactnative-mobile-app-base` 44 | * `$ git submodule init` 45 | * `$ git submodule update` 46 | * `$ ./scripts/get_static_validation.sh` 47 | 48 | Remember that when you copy this repository for a new project you need to add the scripts external module using: 49 | 50 | * `$ git submodule add https://github.com/Seedstars/culture-scripts scripts` 51 | 52 | ## Installation 53 | 54 | ### NODEJS 55 | 56 | * `# wget -qO- https://deb.nodesource.com/setup_4.x | sudo bash -` 57 | * `# apt-get install --yes nodejs` 58 | 59 | ### Main Project 60 | 61 | * `$ npm install` 62 | 63 | 64 | ## Running 65 | 66 | ### Installing React Native / Android Studio 67 | 68 | Follow instructions in React Native website: 69 | 70 | `https://facebook.github.io/react-native/docs/getting-started.html` 71 | 72 | ### Required Configuration 73 | 74 | You need to edit the environment files to be able to use external SDKs (facebook, analysis, onesignal, etc) and also access backend. 75 | The files are in root of project and are called: .env.local_dev, .env.production and .env.staging. 76 | 77 | 78 | ### Development 79 | 80 | Export needed variables 81 | 82 | * `$ export ANDROID_HOME=~/Android/Sdk` 83 | * `$ export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools` 84 | 85 | Start react-native development server 86 | 87 | * `$ npm run start` 88 | 89 | Run app on emulator 90 | 91 | * `$ ENVFILE=.env.local_dev react-native run-android # or any other .env` 92 | 93 | ## Testing 94 | 95 | Confirm that the current app version don't have a staging version in codepush, other wise it will download the update. 96 | 97 | * `code-push deployment ls "Seedstars Base" -k` 98 | 99 | 100 | ### Static analysis 101 | 102 | Frontend (javascript static analysis) 103 | 104 | * `$ npm run lintjs` 105 | 106 | ### Deploy 107 | 108 | * increment app version in `.env.production` 109 | 110 | * `$ cd android && ENVFILE=.env.production ./gradlew assembleRelease` 111 | 112 | ### CodePush Deploy 113 | if you have not registered in Codepush do: 114 | 115 | * `$ code-push register` 116 | 117 | In case you have already registered you should run: 118 | 119 | * `$ code-push collaborator add ` 120 | 121 | After that you can do a release: 122 | 123 | * `$ code-push release-react "Seedstars Base" android -m --description "Message"` 124 | 125 | The version is now in staging. After confirming that everything is ok, promote the version to Production. 126 | 127 | * `$ code-push promote "Seedstars Base" Staging Production` 128 | 129 | In case you face any difficulty, please check the [Documentation](http://microsoft.github.io/code-push/docs/getting-started.html) 130 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.seedstarsbase', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.seedstarsbase', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /android/app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | buildscript { 4 | repositories { 5 | maven { url 'https://maven.fabric.io/public' } 6 | } 7 | dependencies { 8 | // The Fabric Gradle plugin uses an open ended version to react 9 | // quickly to Android tooling updates 10 | classpath 'io.fabric.tools:gradle:1.+' 11 | } 12 | } 13 | apply plugin: 'io.fabric' 14 | repositories { 15 | maven { url 'https://maven.fabric.io/public' } 16 | } 17 | 18 | apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" 19 | 20 | import com.android.build.OutputFile 21 | 22 | /** 23 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 24 | * and bundleReleaseJsAndAssets). 25 | * These basically call `react-native bundle` with the correct arguments during the Android build 26 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 27 | * bundle directly from the development server. Below you can see all the possible configurations 28 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 29 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 30 | * 31 | * project.ext.react = [ 32 | * // the name of the generated asset file containing your JS bundle 33 | * bundleAssetName: "index.android.bundle", 34 | * 35 | * // the entry file for bundle generation 36 | * entryFile: "index.android.js", 37 | * 38 | * // whether to bundle JS and assets in debug mode 39 | * bundleInDebug: false, 40 | * 41 | * // whether to bundle JS and assets in release mode 42 | * bundleInRelease: true, 43 | * 44 | * // whether to bundle JS and assets in another build variant (if configured). 45 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 46 | * // The configuration property can be in the following formats 47 | * // 'bundleIn${productFlavor}${buildType}' 48 | * // 'bundleIn${buildType}' 49 | * // bundleInFreeDebug: true, 50 | * // bundleInPaidRelease: true, 51 | * // bundleInBeta: true, 52 | * 53 | * // the root of your project, i.e. where "package.json" lives 54 | * root: "../../", 55 | * 56 | * // where to put the JS bundle asset in debug mode 57 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 58 | * 59 | * // where to put the JS bundle asset in release mode 60 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 61 | * 62 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 63 | * // require('./image.png')), in debug mode 64 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 65 | * 66 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 67 | * // require('./image.png')), in release mode 68 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 69 | * 70 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 71 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 72 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 73 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 74 | * // for example, you might want to remove it from here. 75 | * inputExcludes: ["android/**", "ios/**"], 76 | * 77 | * // override which node gets called and with what additional arguments 78 | * nodeExecutableAndArgs: ["node"] 79 | * 80 | * // supply additional arguments to the packager 81 | * extraPackagerArgs: [] 82 | * ] 83 | */ 84 | 85 | apply from: "../../node_modules/react-native/react.gradle" 86 | apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" 87 | apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" 88 | 89 | /** 90 | * Set this to true to create two separate APKs instead of one: 91 | * - An APK that only works on ARM devices 92 | * - An APK that only works on x86 devices 93 | * The advantage is the size of the APK is reduced by about 4MB. 94 | * Upload all the APKs to the Play Store and people will download 95 | * the correct one based on the CPU architecture of their device. 96 | */ 97 | def enableSeparateBuildPerCPUArchitecture = true 98 | 99 | /** 100 | * Run Proguard to shrink the Java bytecode in release builds. 101 | */ 102 | def enableProguardInReleaseBuilds = false 103 | 104 | android { 105 | compileSdkVersion 23 106 | buildToolsVersion "23.0.2" 107 | 108 | defaultConfig { 109 | applicationId "com.seedstarsbase" 110 | minSdkVersion 16 111 | targetSdkVersion 22 // KEEP THIS VERSION 112 | versionCode 13 113 | versionName project.env.get("APP_VERSION_NAME") 114 | ndk { 115 | abiFilters "armeabi-v7a", "x86" 116 | } 117 | manifestPlaceholders = [manifestApplicationId: "${applicationId}", 118 | onesignal_app_id: project.env.get("ONESIGNAL_ID"), 119 | onesignal_google_project_number: project.env.get("GOOGLE_PROJECT_NUMBER")] 120 | multiDexEnabled true 121 | } 122 | signingConfigs { 123 | release { 124 | storeFile file(MYAPP_RELEASE_STORE_FILE) 125 | storePassword MYAPP_RELEASE_STORE_PASSWORD 126 | keyAlias MYAPP_RELEASE_KEY_ALIAS 127 | keyPassword MYAPP_RELEASE_KEY_PASSWORD 128 | } 129 | } 130 | splits { 131 | abi { 132 | reset() 133 | enable enableSeparateBuildPerCPUArchitecture 134 | universalApk false // If true, also generate a universal APK 135 | include "armeabi-v7a", "x86" 136 | } 137 | } 138 | buildTypes { 139 | debug { 140 | buildConfigField "String", "CODEPUSH_KEY", '"'+project.env.get("CODE_PUSH_STAGING_KEY")+'"' 141 | } 142 | release { 143 | minifyEnabled enableProguardInReleaseBuilds 144 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 145 | signingConfig signingConfigs.release 146 | buildConfigField "String", "CODEPUSH_KEY", '"'+project.env.get("CODE_PUSH_PRODUCTION_KEY")+'"' 147 | } 148 | } 149 | // applicationVariants are e.g. debug, release 150 | applicationVariants.all { variant -> 151 | variant.outputs.each { output -> 152 | // For each separate APK per architecture, set a unique version code as described here: 153 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 154 | def versionCodes = ["armeabi-v7a":1, "x86":2] 155 | def abi = output.getFilter(OutputFile.ABI) 156 | if (abi != null) { // null for the universal-debug, universal-release variants 157 | output.versionCodeOverride = 158 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 159 | } 160 | } 161 | } 162 | } 163 | 164 | dependencies { 165 | compile project(':react-native-code-push') 166 | compile project(':react-native-branch') 167 | compile project(':react-native-google-analytics-bridge') 168 | compile project(':react-native-fbsdk') 169 | compile fileTree(dir: "libs", include: ["*.jar"]) 170 | compile "com.android.support:appcompat-v7:23.0.1" 171 | compile "com.facebook.react:react-native:+" // From node_modules 172 | compile project(':react-native-vector-icons') 173 | compile project(':react-native-onesignal') 174 | compile project(':react-native-config') 175 | compile "com.google.android.gms:play-services-base:+" 176 | compile 'com.google.android.gms:play-services-location:+' 177 | compile 'com.google.android.gms:play-services-maps:+' 178 | compile project(':react-native-fabric') 179 | compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') { 180 | transitive = true; 181 | } 182 | 183 | 184 | } 185 | 186 | // Run this once to be able to run the application with BUCK 187 | // puts all compile dependencies into folder libs for BUCK to use 188 | task copyDownloadableDepsToLibs(type: Copy) { 189 | from configurations.compile 190 | into 'libs' 191 | } 192 | -------------------------------------------------------------------------------- /android/app/fabric.properties: -------------------------------------------------------------------------------- 1 | #Contains API Secret used to validate your application. Commit to internal source control; avoid making secret public. 2 | #Fri Nov 18 16:17:25 WET 2016 3 | apiSecret=7f26923ae8c37b784d77d1707495dc108542dc56c03af48c9c9c58c2fdd87340 4 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/seedstarsbase/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.seedstarsbase; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import io.branch.rnbranch.RNBranchPackage; 5 | import io.branch.rnbranch.*; 6 | import com.idehub.GoogleAnalyticsBridge.GoogleAnalyticsBridgePackage; 7 | import com.facebook.reactnative.androidsdk.FBSDKPackage; 8 | import android.content.Intent; 9 | 10 | public class MainActivity extends ReactActivity { 11 | 12 | @Override 13 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 14 | super.onActivityResult(requestCode, resultCode, data); 15 | MainApplication.getCallbackManager().onActivityResult(requestCode, resultCode, data); 16 | } 17 | /** 18 | * Returns the name of the main component registered from JavaScript. 19 | * This is used to schedule rendering of the component. 20 | */ 21 | @Override 22 | protected String getMainComponentName() { 23 | return "seedstarsbase"; 24 | } 25 | @Override 26 | protected void onStart() { 27 | super.onStart(); 28 | RNBranchModule.initSession(this.getIntent().getData(), this); 29 | } 30 | 31 | @Override 32 | public void onNewIntent(Intent intent) { 33 | this.setIntent(intent); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/seedstarsbase/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.seedstarsbase; 2 | 3 | import android.app.Application; 4 | 5 | import com.smixx.fabric.FabricPackage; 6 | import com.crashlytics.android.Crashlytics; 7 | import io.fabric.sdk.android.Fabric; 8 | import com.facebook.common.logging.FLog; 9 | import com.facebook.react.ReactApplication; 10 | import com.microsoft.codepush.react.CodePush; 11 | import com.facebook.react.ReactNativeHost; 12 | import com.facebook.react.ReactPackage; 13 | import com.facebook.react.shell.MainReactPackage; 14 | 15 | import com.facebook.CallbackManager; 16 | import com.facebook.FacebookSdk; 17 | import com.facebook.appevents.AppEventsLogger; 18 | import com.facebook.reactnative.androidsdk.FBSDKPackage; 19 | import io.branch.rnbranch.*; 20 | import com.lugg.ReactNativeConfig.ReactNativeConfigPackage; 21 | 22 | import com.oblador.vectoricons.VectorIconsPackage; 23 | import com.idehub.GoogleAnalyticsBridge.GoogleAnalyticsBridgePackage; 24 | 25 | import com.geektime.reactnativeonesignal.ReactNativeOneSignalPackage; 26 | 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | public class MainApplication extends Application implements ReactApplication { 31 | 32 | private static CallbackManager mCallbackManager = CallbackManager.Factory.create(); 33 | 34 | protected static CallbackManager getCallbackManager() { 35 | return mCallbackManager; 36 | } 37 | 38 | 39 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 40 | 41 | @Override 42 | protected String getJSBundleFile() { 43 | return CodePush.getJSBundleFile(); 44 | } 45 | 46 | @Override 47 | protected boolean getUseDeveloperSupport() { 48 | return BuildConfig.DEBUG; 49 | } 50 | 51 | @Override 52 | protected List getPackages() { 53 | return Arrays.asList( 54 | new MainReactPackage(), 55 | new ReactNativeOneSignalPackage(), 56 | new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG), 57 | new FBSDKPackage(mCallbackManager), 58 | new VectorIconsPackage(), 59 | new GoogleAnalyticsBridgePackage(), 60 | new RNBranchPackage(), 61 | new ReactNativeConfigPackage(), 62 | new FabricPackage() 63 | ); 64 | } 65 | }; 66 | 67 | @Override 68 | public ReactNativeHost getReactNativeHost() { 69 | return mReactNativeHost; 70 | } 71 | 72 | @Override 73 | public void onCreate() { 74 | super.onCreate(); 75 | FacebookSdk.sdkInitialize(getApplicationContext()); 76 | AppEventsLogger.activateApp(this); 77 | Fabric.with(this, new Crashlytics()); 78 | FLog.setLoggingDelegate(ReactNativeFabricLogger.getInstance()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/seedstarsbase/ReactNativeFabricLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | package com.seedstarsbase; 11 | 12 | import com.facebook.common.logging.LoggingDelegate; 13 | import com.crashlytics.android.Crashlytics; 14 | 15 | import java.io.PrintWriter; 16 | import java.io.StringWriter; 17 | 18 | import android.util.Log; 19 | 20 | /** 21 | * Default implementation of {@link LoggingDelegate}. 22 | */ 23 | public class ReactNativeFabricLogger implements LoggingDelegate { 24 | 25 | public static final ReactNativeFabricLogger sInstance = new ReactNativeFabricLogger(); 26 | 27 | private String mApplicationTag = "unknown"; 28 | private int mMinimumLoggingLevel = Log.WARN; 29 | 30 | public static ReactNativeFabricLogger getInstance() { 31 | return sInstance; 32 | } 33 | 34 | private ReactNativeFabricLogger() { 35 | } 36 | 37 | /** 38 | * Sets an application tag that is used for checking if a log line is loggable and also 39 | * to prefix to all log lines. 40 | * 41 | * @param tag the tag 42 | */ 43 | public void setApplicationTag(String tag) { 44 | mApplicationTag = tag; 45 | } 46 | 47 | 48 | @Override 49 | public void setMinimumLoggingLevel(int level) { 50 | mMinimumLoggingLevel = level; 51 | } 52 | 53 | @Override 54 | public int getMinimumLoggingLevel() { 55 | return mMinimumLoggingLevel; 56 | } 57 | 58 | @Override 59 | public boolean isLoggable(int level) { 60 | return mMinimumLoggingLevel <= level; 61 | } 62 | 63 | @Override 64 | public void v(String tag, String msg) { 65 | println(Log.VERBOSE, tag, msg); 66 | } 67 | 68 | @Override 69 | public void v(String tag, String msg, Throwable tr) { 70 | println(Log.VERBOSE, tag, msg, tr); 71 | } 72 | 73 | @Override 74 | public void d(String tag, String msg) { 75 | println(Log.DEBUG, tag, msg); 76 | } 77 | 78 | @Override 79 | public void d(String tag, String msg, Throwable tr) { 80 | println(Log.DEBUG, tag, msg, tr); 81 | } 82 | 83 | @Override 84 | public void i(String tag, String msg) { 85 | println(Log.INFO, tag, msg); 86 | } 87 | 88 | @Override 89 | public void i(String tag, String msg, Throwable tr) { 90 | println(Log.INFO, tag, msg, tr); 91 | } 92 | 93 | @Override 94 | public void w(String tag, String msg) { 95 | println(Log.WARN, tag, msg); 96 | } 97 | 98 | @Override 99 | public void w(String tag, String msg, Throwable tr) { 100 | println(Log.WARN, tag, msg, tr); 101 | } 102 | 103 | @Override 104 | public void e(String tag, String msg) { 105 | println(Log.ERROR, tag, msg); 106 | } 107 | 108 | @Override 109 | public void e(String tag, String msg, Throwable tr) { 110 | println(Log.ERROR, tag, msg, tr); 111 | } 112 | 113 | /** 114 | *

Note: this gets forwarded to {@code android.util.Log.e} as {@code android.util.Log.wtf} 115 | * might crash the app. 116 | */ 117 | @Override 118 | public void wtf(String tag, String msg) { 119 | println(Log.ERROR, tag, msg); 120 | } 121 | 122 | /** 123 | *

Note: this gets forwarded to {@code android.util.Log.e} as {@code android.util.Log.wtf} 124 | * might crash the app. 125 | */ 126 | @Override 127 | public void wtf(String tag, String msg, Throwable tr) { 128 | println(Log.ERROR, tag, msg, tr); 129 | } 130 | 131 | @Override 132 | public void log(int priority, String tag, String msg) { 133 | println(priority, tag, msg); 134 | } 135 | 136 | private void println(int priority, String tag, String msg) { 137 | Log.println(priority, prefixTag(tag), msg); 138 | if (BuildConfig.DEBUG) { 139 | Log.println(priority, prefixTag(tag), msg); 140 | } else { 141 | Crashlytics.log(priority, prefixTag(tag), msg); 142 | } 143 | } 144 | 145 | private void println(int priority, String tag, String msg, Throwable tr) { 146 | Log.println(priority, prefixTag(tag), getMsg(msg, tr)); 147 | if (BuildConfig.DEBUG) { 148 | Log.println(priority, prefixTag(tag), getMsg(msg, tr)); 149 | } else { 150 | Crashlytics.log(priority, prefixTag(tag), msg); 151 | } 152 | } 153 | 154 | private String prefixTag(String tag) { 155 | if (mApplicationTag != null) { 156 | return mApplicationTag + ":" + tag; 157 | } else { 158 | return tag; 159 | } 160 | } 161 | 162 | private static String getMsg(String msg, Throwable tr) { 163 | return msg + '\n' + getStackTraceString(tr); 164 | } 165 | 166 | private static String getStackTraceString(Throwable tr) { 167 | if (tr == null) { 168 | return ""; 169 | } 170 | StringWriter sw = new StringWriter(); 171 | PrintWriter pw = new PrintWriter(sw); 172 | tr.printStackTrace(pw); 173 | return sw.toString(); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seedstars/reactnative-mobile-app-base/d2476e6932cfaee118461fe74f8c863c4d3c9dc9/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seedstars/reactnative-mobile-app-base/d2476e6932cfaee118461fe74f8c863c4d3c9dc9/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seedstars/reactnative-mobile-app-base/d2476e6932cfaee118461fe74f8c863c4d3c9dc9/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seedstars/reactnative-mobile-app-base/d2476e6932cfaee118461fe74f8c863c4d3c9dc9/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My Store Foo 5 | PUT_API_HERE 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | 22 | MYAPP_RELEASE_STORE_FILE=seedstarsbase-beta-key.keystore 23 | MYAPP_RELEASE_KEY_ALIAS=seedstarsbase-beta 24 | MYAPP_RELEASE_STORE_PASSWORD=seedstarsbase 25 | MYAPP_RELEASE_KEY_PASSWORD=seedstarsbase 26 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seedstars/reactnative-mobile-app-base/d2476e6932cfaee118461fe74f8c863c4d3c9dc9/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 03 11:18:56 WET 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'seedstarsbase' 2 | 3 | include ':app' 4 | include ':react-native-code-push' 5 | project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') 6 | include ':react-native-branch' 7 | project(':react-native-branch').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-branch/android') 8 | include ':react-native-google-analytics-bridge' 9 | project(':react-native-google-analytics-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-analytics-bridge/android') 10 | include ':react-native-fbsdk' 11 | include ':react-native-vector-icons' 12 | project(':react-native-fbsdk').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fbsdk/android') 13 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 14 | include ':react-native-onesignal' 15 | project(':react-native-onesignal').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-onesignal/android') 16 | include ':react-native-config' 17 | project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') 18 | include ':app', ':react-native-maps' 19 | project(':react-native-maps').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-maps/android') 20 | include ':react-native-fabric' 21 | project(':react-native-fabric').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fabric/android') -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | 3 | import MainApp from './js/App'; 4 | 5 | 6 | AppRegistry.registerComponent('seedstarsbase', () => { return MainApp; }); 7 | -------------------------------------------------------------------------------- /js/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | StatusBar 5 | } from 'react-native'; 6 | import { Provider } from 'react-redux'; 7 | import codePush from 'react-native-code-push'; 8 | 9 | import AppStyles from './styles'; 10 | import configureStore from './store'; 11 | import MainNavigation from './containers/MainNavigation'; 12 | import AnalyticsProvider from './utils/AnalyticsUtils'; 13 | import { DARKER_PRIMARY } from './styles/colors'; 14 | 15 | const codePushOptions = { 16 | checkFrequency: codePush.CheckFrequency.ON_APP_RESUME, 17 | installMode: codePush.InstallMode.IMMEDIATE 18 | }; 19 | const store = configureStore(); 20 | 21 | class App extends Component { 22 | render() { 23 | return ( 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | } 38 | 39 | export default codePush(codePushOptions)(App); 40 | -------------------------------------------------------------------------------- /js/actions/auth.js: -------------------------------------------------------------------------------- 1 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 2 | import OneSignal from 'react-native-onesignal'; 3 | import { Crashlytics } from 'react-native-fabric'; 4 | import Config from 'react-native-config'; 5 | import Storage from 'react-native-simple-store'; 6 | import { Alert } from 'react-native'; 7 | import branch from 'react-native-branch'; 8 | import { Buffer } from 'buffer'; 9 | import { 10 | AUTH_LOGIN_USER_REQUEST, 11 | AUTH_LOGIN_USER_SUCCESS, 12 | AUTH_LOGIN_USER_FAILURE, 13 | AUTH_LOGIN_USER_EXPIRED, 14 | AUTH_LOGOUT_USER, 15 | AUTH_SAVE_USERS_INFORMATION, 16 | AUTH_SIGNUP_REQUEST, 17 | AUTH_SIGNUP_SUCCESS, 18 | SUBMIT_SIGNUP_FORM_SAVE, 19 | SUBMIT_LOGIN_FORM_SAVE, 20 | AUTH_LOGIN_CLEAN_ERRORS 21 | } from '../constants'; 22 | import { SERVER_URL } from '../utils/config'; 23 | import { checkHttpStatus, parseJSON } from '../utils'; 24 | 25 | const { reset } = navigationActions; 26 | 27 | 28 | export function userLoginRequest() { 29 | return { 30 | type: AUTH_LOGIN_USER_REQUEST 31 | }; 32 | } 33 | 34 | export function userLogoutRequest() { 35 | return { 36 | type: AUTH_LOGOUT_USER 37 | }; 38 | } 39 | 40 | export function userLoginSuccess(token, user, outdated) { 41 | return { 42 | type: AUTH_LOGIN_USER_SUCCESS, 43 | payload: { 44 | token, 45 | user, 46 | outdated 47 | } 48 | }; 49 | } 50 | export function cleanErrors() { 51 | return { 52 | type: AUTH_LOGIN_CLEAN_ERRORS 53 | }; 54 | } 55 | 56 | export function userLoginFailure(error, errorObject) { 57 | return { 58 | type: AUTH_LOGIN_USER_FAILURE, 59 | payload: { 60 | error, 61 | errorObject 62 | } 63 | }; 64 | } 65 | 66 | export function userLoginExpired(error, errorObject) { 67 | return { 68 | type: AUTH_LOGIN_USER_EXPIRED, 69 | payload: { 70 | error, 71 | errorObject 72 | } 73 | }; 74 | } 75 | 76 | export function saveUserInformation(user) { 77 | return { 78 | type: AUTH_SAVE_USERS_INFORMATION, 79 | payload: { 80 | user, 81 | } 82 | }; 83 | } 84 | 85 | export function signUpSaveForms(data) { 86 | return { 87 | type: SUBMIT_SIGNUP_FORM_SAVE, 88 | payload: { 89 | data 90 | } 91 | }; 92 | } 93 | 94 | export function loginSaveForms(data) { 95 | return { 96 | type: SUBMIT_LOGIN_FORM_SAVE, 97 | payload: { 98 | data 99 | } 100 | }; 101 | } 102 | 103 | export function signUpRequest() { 104 | return { 105 | type: AUTH_SIGNUP_REQUEST 106 | }; 107 | } 108 | 109 | export function signUpSuccess(token, user, outdated) { 110 | return { 111 | type: AUTH_SIGNUP_SUCCESS, 112 | payload: { 113 | token, 114 | user, 115 | outdated, 116 | } 117 | }; 118 | } 119 | 120 | export function setOneSignalId(token, oneSignalId) { 121 | return (dispatch) => { 122 | return fetch(`${SERVER_URL}/api/v1/accounts/onesignal/`, { 123 | method: 'post', 124 | credentials: 'include', 125 | headers: { 126 | 'Accept': 'application/json', 127 | 'Content-Type': 'application/json', 128 | 'Authorization': `Token ${token}` 129 | }, 130 | body: JSON.stringify({ 131 | one_signal_id: oneSignalId 132 | } 133 | ) 134 | }) 135 | .then(checkHttpStatus) 136 | .done(); 137 | }; 138 | } 139 | 140 | export function getOneSignal(token, oneSignalId) { 141 | return (dispatch) => { 142 | OneSignal.configure({ 143 | onIdsAvailable: (device) => { 144 | if (typeof oneSignalId !== 'undefined' && device.userId !== oneSignalId) { 145 | dispatch(setOneSignalId(token, device.userId)); 146 | } 147 | } 148 | }); 149 | }; 150 | } 151 | 152 | export function loginFacebook(token) { 153 | return (dispatch) => { 154 | dispatch(userLoginRequest()); 155 | 156 | return branch.getFirstReferringParams() 157 | .then((installParams) => { 158 | return fetch(`${SERVER_URL}/api/v1/accounts/login-facebook/`, { 159 | method: 'post', 160 | credentials: 'include', 161 | headers: { 162 | 'Accept': 'application/json', 163 | 'Content-Type': 'application/json' 164 | }, 165 | body: JSON.stringify({ 166 | access_token: token, 167 | app_version: Config.APP_VERSION_NAME, 168 | branch_data: installParams, 169 | 170 | }) 171 | }); 172 | }) 173 | .then(checkHttpStatus) 174 | .then(parseJSON) 175 | .then((response) => { 176 | try { 177 | Storage.save('user', { 178 | token: response.token, 179 | user: response.user, 180 | }); 181 | 182 | Crashlytics.setUserName(`${response.user.first_name} ${response.user.last_name}`); 183 | Crashlytics.setUserEmail(response.user.email); 184 | Crashlytics.setUserIdentifier(response.user.facebook_uid); 185 | 186 | dispatch(reset([{ 187 | key: 'dashboard', 188 | index: 0 189 | }], 'global')); 190 | 191 | dispatch(userLoginSuccess(response.token, response.user, response.outdated)); 192 | dispatch(getOneSignal(response.token, response.user.one_signal_id)); 193 | } catch (e) { 194 | dispatch(userLoginFailure(403, e)); 195 | } 196 | }).catch((error) => { 197 | if (error && typeof error.response !== 'undefined' && error.response.status === 401) { 198 | // token issues 199 | Storage.delete('user'); 200 | 201 | dispatch(userLoginFailure(401, error)); 202 | dispatch(reset([{ 203 | key: 'dashboard', 204 | index: 0 205 | }], 'global')); 206 | } else if (error && typeof error.response !== 'undefined' && error.response.status === 400) { 207 | // form validation issues, so we should have a json with errors 208 | error.response.json().then((data) => { 209 | dispatch(userLoginFailure(400, data)); 210 | }); 211 | } else if (error && typeof error.response !== 'undefined' && error.response.status >= 500) { 212 | // server crash 213 | Alert.alert('Server Error', 'A server error occurred while sending your data!'); 214 | dispatch(userLoginFailure(500, null)); 215 | } else { 216 | // other error, probably network fail just pass null 217 | Alert.alert('Connection Error', 'An error occurred while sending your data!'); 218 | dispatch(userLoginFailure(null, null)); 219 | } 220 | }) 221 | .done(); 222 | }; 223 | } 224 | 225 | export function loginUser(formData) { 226 | return (dispatch) => { 227 | dispatch(userLoginRequest()); 228 | const buffer = new Buffer(`${formData.email}:${formData.password}`); 229 | const auth = buffer.toString('base64'); 230 | 231 | return branch.getFirstReferringParams() 232 | .then((installParams) => { 233 | return fetch(`${SERVER_URL}/api/v1/accounts/login/`, { 234 | method: 'post', 235 | credentials: 'include', 236 | headers: { 237 | 'Accept': 'application/json', 238 | 'Content-Type': 'application/json', 239 | 'Authorization': `Basic ${auth}` 240 | }, 241 | body: JSON.stringify({ 242 | app_version: Config.APP_VERSION_NAME, 243 | branch_data: installParams, 244 | 245 | }) 246 | }); 247 | }) 248 | .then(checkHttpStatus) 249 | .then(parseJSON) 250 | .then((response) => { 251 | try { 252 | Storage.save('user', { 253 | token: response.token, 254 | user: response.user 255 | }); 256 | Crashlytics.setUserName(`${response.user.first_name} ${response.user.last_name}`); 257 | Crashlytics.setUserEmail(response.user.email); 258 | Crashlytics.setUserIdentifier(response.user.facebook_uid); 259 | 260 | dispatch(reset([{ 261 | key: 'dashboard', 262 | index: 0 263 | }], 'global')); 264 | 265 | dispatch(userLoginSuccess(response.token, response.user, response.outdated)); 266 | dispatch(getOneSignal(response.token, response.user.one_signal_id)); 267 | } catch (e) { 268 | dispatch(userLoginFailure(403, e)); 269 | } 270 | }).catch((error) => { 271 | if (error && typeof error.response !== 'undefined' && error.response.status === 401) { 272 | // token issues 273 | 274 | error.response.json().then((data) => { 275 | dispatch(userLoginFailure(401, data)); 276 | }); 277 | Storage.delete('user'); 278 | } else if (error && typeof error.response !== 'undefined' && error.response.status === 400) { 279 | // form validation issues, so we should have a json with errors 280 | error.response.json().then((data) => { 281 | dispatch(userLoginFailure(400, data)); 282 | }); 283 | } else if (error && typeof error.response !== 'undefined' && error.response.status >= 500) { 284 | // server crash 285 | Alert.alert('Server Error', 'A server error occurred while sending your data!'); 286 | dispatch(userLoginFailure(500, null)); 287 | } else { 288 | // other error, probably network fail just pass null 289 | Alert.alert('Connection Error', 'An error occurred while sending your data!'); 290 | dispatch(userLoginFailure(null, null)); 291 | } 292 | }) 293 | .done(); 294 | }; 295 | } 296 | 297 | 298 | export function signUp(formData) { 299 | return (dispatch) => { 300 | dispatch(signUpRequest()); 301 | return branch.getFirstReferringParams() 302 | .then((installParams) => { 303 | return fetch(`${SERVER_URL}/api/v1/accounts/register/`, { 304 | method: 'post', 305 | credentials: 'include', 306 | headers: { 307 | 'Accept': 'application/json', 308 | 'Content-Type': 'application/json' 309 | }, 310 | body: JSON.stringify({ 311 | app_version: Config.APP_VERSION_NAME, 312 | branch_data: installParams, 313 | ...formData 314 | }) 315 | }); 316 | }) 317 | .then(checkHttpStatus) 318 | .then(parseJSON) 319 | .then((response) => { 320 | try { 321 | Storage.save('user', { 322 | token: response.token, 323 | user: response.user 324 | }); 325 | Crashlytics.setUserName(`${response.user.first_name} ${response.user.last_name}`); 326 | Crashlytics.setUserEmail(response.user.email); 327 | Crashlytics.setUserIdentifier(response.user.facebook_uid); 328 | 329 | dispatch(reset([{ 330 | key: 'dashboard', 331 | index: 0 332 | }], 'global')); 333 | 334 | dispatch(userLoginSuccess(response.token, response.user, response.outdated)); 335 | dispatch(getOneSignal(response.token, response.user.one_signal_id)); 336 | } catch (e) { 337 | dispatch(userLoginFailure(403, e)); 338 | } 339 | }).catch((error) => { 340 | if (error && typeof error.response !== 'undefined' && error.response.status === 401) { 341 | // token issues 342 | Storage.delete('user'); 343 | error.response.json().then((data) => { 344 | dispatch(userLoginFailure(401, data)); 345 | }); 346 | } else if (error && typeof error.response !== 'undefined' && error.response.status === 400) { 347 | // form validation issues, so we should have a json with errors 348 | error.response.json().then((data) => { 349 | dispatch(userLoginFailure(400, data)); 350 | }); 351 | } else if (error && typeof error.response !== 'undefined' && error.response.status >= 500) { 352 | // server crash 353 | Alert.alert('Server Error', 'A server error occurred while sending your data!'); 354 | dispatch(userLoginFailure(500, null)); 355 | } else { 356 | // other error, probably network fail just pass null 357 | Alert.alert('Connection Error', 'An error occurred while sending your data!'); 358 | dispatch(userLoginFailure(null, null)); 359 | } 360 | }) 361 | .done(); 362 | }; 363 | } 364 | -------------------------------------------------------------------------------- /js/actions/forms.js: -------------------------------------------------------------------------------- 1 | import { Alert } from 'react-native'; 2 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 3 | import Storage from 'react-native-simple-store'; 4 | import { 5 | SUBMIT_ORDER_DEVICE_FORM_REQUEST, 6 | SUBMIT_ORDER_DEVICE_FORM_SAVE, 7 | SUBMIT_ORDER_DEVICE_FORM_SUCCESS, 8 | SUBMIT_ORDER_DEVICE_FORM_FAILURE 9 | } from '../constants'; 10 | import { SERVER_URL } from '../utils/config'; 11 | import { checkHttpStatus } from '../utils'; 12 | import { userLoginExpired } from './auth'; 13 | import { closeBuyDeviceModal } from './modals'; 14 | 15 | 16 | const { replaceAtIndex } = navigationActions; 17 | 18 | export function orderDeviceSaveForms(data) { 19 | return { 20 | type: SUBMIT_ORDER_DEVICE_FORM_SAVE, 21 | payload: { 22 | data 23 | } 24 | }; 25 | } 26 | 27 | export function orderDevicePostFormsRequest(data) { 28 | return { 29 | type: SUBMIT_ORDER_DEVICE_FORM_REQUEST, 30 | payload: { 31 | data 32 | } 33 | }; 34 | } 35 | 36 | 37 | export function orderDevicePostFormsSuccess(data) { 38 | return { 39 | type: SUBMIT_ORDER_DEVICE_FORM_SUCCESS, 40 | payload: { 41 | data 42 | } 43 | }; 44 | } 45 | 46 | export function orderDevicePostFormsFailure(errorCode, errorObject) { 47 | return { 48 | type: SUBMIT_ORDER_DEVICE_FORM_FAILURE, 49 | payload: { 50 | errorCode, 51 | errorObject 52 | } 53 | }; 54 | } 55 | 56 | export function orderDevicePostForms(token, formData, navigatorIndex) { 57 | return (dispatch) => { 58 | dispatch(orderDevicePostFormsRequest(formData)); 59 | return fetch(`${SERVER_URL}/api/v1/device/order_device/`, { 60 | method: 'post', 61 | credentials: 'include', 62 | headers: { 63 | 'Accept': 'application/json', 64 | 'Content-Type': 'application/json', 65 | 'Authorization': `Token ${token}` 66 | }, 67 | body: JSON.stringify(formData) 68 | }) 69 | .then(checkHttpStatus) 70 | .then((response) => { 71 | dispatch(closeBuyDeviceModal()); 72 | // dispatch natigator replace because if we got here action was successful and we can't go back 73 | dispatch(replaceAtIndex(navigatorIndex, { 74 | key: 'deviceOrdered', 75 | title: 'Done' 76 | }, 'global')); 77 | // dispatch success action 78 | dispatch(orderDevicePostFormsSuccess({})); 79 | }).catch((error) => { 80 | if (error && typeof error.response !== 'undefined' && error.response.status === 401) { 81 | dispatch(closeBuyDeviceModal()); 82 | // token issues 83 | Storage.delete('user'); 84 | dispatch(userLoginExpired(401, error)); 85 | dispatch(orderDevicePostFormsFailure(400, {})); 86 | dispatch(replaceAtIndex(navigatorIndex, { 87 | key: 'login', 88 | title: 'login' 89 | }, 'global')); 90 | } else if (error && typeof error.response !== 'undefined' && error.response.status === 400) { 91 | dispatch(closeBuyDeviceModal()); 92 | // form validation issues, so we should have a json with errors 93 | error.response.json().then((data) => { 94 | dispatch(orderDevicePostFormsFailure(400, data)); 95 | }); 96 | } else if (error && typeof error.response !== 'undefined' && error.response.status >= 500) { 97 | dispatch(closeBuyDeviceModal()); 98 | // server crash 99 | Alert.alert('Server Error', 'A server error occurred while sending your data!'); 100 | dispatch(orderDevicePostFormsFailure(500, null)); 101 | } else { 102 | dispatch(closeBuyDeviceModal()); 103 | // other error, probably network fail just pass null 104 | Alert.alert('Connection Error', 'An error occurred while sending your data!'); 105 | dispatch(orderDevicePostFormsFailure(null, null)); 106 | } 107 | }) 108 | .done(); 109 | }; 110 | } 111 | 112 | export function orderDeviceValidatePostForms(token, formData, email, navigatorIndex) { 113 | return (dispatch) => { 114 | dispatch(orderDevicePostFormsRequest(formData)); 115 | return fetch(`${SERVER_URL}/api/v1/device/validate_order_device_data/`, { 116 | method: 'post', 117 | credentials: 'include', 118 | headers: { 119 | 'Accept': 'application/json', 120 | 'Content-Type': 'application/json', 121 | 'Authorization': `Token ${token}` 122 | }, 123 | body: JSON.stringify(formData) 124 | }) 125 | .then(checkHttpStatus) 126 | .then((response) => { 127 | dispatch(closeBuyDeviceModal()); 128 | dispatch(orderDevicePostForms(token, formData, navigatorIndex)); 129 | 130 | // dispatch success action 131 | dispatch(orderDevicePostFormsSuccess({})); 132 | }).catch((error) => { 133 | if (error && typeof error.response !== 'undefined' && error.response.status === 401) { 134 | dispatch(closeBuyDeviceModal()); 135 | // token issues 136 | dispatch(userLoginExpired(401, error)); 137 | dispatch(orderDevicePostFormsFailure(400, {})); 138 | 139 | Storage.delete('user'); 140 | dispatch(replaceAtIndex(navigatorIndex, { 141 | key: 'login', 142 | title: 'login' 143 | }, 'global')); 144 | } else if (error && typeof error.response !== 'undefined' && error.response.status === 400) { 145 | dispatch(closeBuyDeviceModal()); 146 | // form validation issues, so we should have a json with errors 147 | error.response.json().then((data) => { 148 | dispatch(orderDevicePostFormsFailure(400, data)); 149 | }); 150 | } else if (error && typeof error.response !== 'undefined' && error.response.status >= 500) { 151 | dispatch(closeBuyDeviceModal()); 152 | // server crash 153 | Alert.alert('Server Error', 'A server error occurred while sending your data!'); 154 | dispatch(orderDevicePostFormsFailure(500, null)); 155 | } else { 156 | dispatch(closeBuyDeviceModal()); 157 | // other error, probably network fail just pass null 158 | Alert.alert('Connection Error', 'An error occurred while sending your data!'); 159 | dispatch(orderDevicePostFormsFailure(null, null)); 160 | } 161 | }) 162 | .done(); 163 | }; 164 | } 165 | -------------------------------------------------------------------------------- /js/actions/modals.js: -------------------------------------------------------------------------------- 1 | import { 2 | BUYDEVICE_MODAL_OPEN, 3 | BUYDEVICE_MODAL_CLOSE, 4 | LOADING_MODAL_OPEN, 5 | LOADING_MODAL_CLOSE, 6 | WARNING_MODAL_OPEN, 7 | WARNING_MODAL_CLOSE 8 | } from '../constants'; 9 | 10 | export function openBuyDeviceModal() { 11 | return (dispatch) => { 12 | dispatch({ 13 | type: BUYDEVICE_MODAL_OPEN, 14 | payload: { 15 | visible: true, 16 | } 17 | }); 18 | }; 19 | } 20 | 21 | export function closeBuyDeviceModal() { 22 | return (dispatch) => { 23 | dispatch({ 24 | type: BUYDEVICE_MODAL_CLOSE 25 | }); 26 | }; 27 | } 28 | 29 | export function openLoadingModal(message) { 30 | return { 31 | type: LOADING_MODAL_OPEN, 32 | payload: { 33 | visible: true, 34 | message 35 | } 36 | }; 37 | } 38 | 39 | export function closeLoadingModal() { 40 | return { 41 | type: LOADING_MODAL_CLOSE 42 | }; 43 | } 44 | 45 | export function openWarningModal(title, message, buttons) { 46 | return (dispatch) => { 47 | dispatch({ 48 | type: WARNING_MODAL_OPEN, 49 | payload: { 50 | visible: true, 51 | title, 52 | message, 53 | buttons 54 | } 55 | }); 56 | }; 57 | } 58 | 59 | export function closeWarningModal() { 60 | return (dispatch) => { 61 | dispatch({ 62 | type: WARNING_MODAL_CLOSE 63 | }); 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /js/components/BottomButton/bottomButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, TouchableOpacity, Text } from 'react-native'; 3 | 4 | import styles from './styles'; 5 | 6 | export default class bottomButton extends Component { 7 | static propTypes = { 8 | onPress: React.PropTypes.func, 9 | text: React.PropTypes.string, 10 | text2: React.PropTypes.string, 11 | backgroundColor: React.PropTypes.shape() 12 | }; 13 | render() { 14 | return ( 15 | 16 | 19 | 20 | {this.props.text} 21 | {this.props.text2} 22 | 23 | 24 | 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /js/components/BottomButton/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Dimensions } from 'react-native'; 2 | 3 | const styles = StyleSheet.create({ 4 | buttonText: { 5 | fontSize: 16, 6 | color: 'white', 7 | fontWeight: '100' 8 | }, 9 | loginButton: { 10 | alignItems: 'center', 11 | height: 50, 12 | paddingTop: 15, 13 | borderTopWidth: 1, 14 | borderTopColor: 'rgba(255,255,255, 0.8)', 15 | opacity: 0.8, 16 | paddingRight: 1, 17 | overflow: 'hidden', 18 | width: Dimensions.get('window').width, 19 | }, 20 | textContainer: { 21 | flexDirection: 'row' 22 | }, 23 | textBold: { 24 | fontWeight: '900' 25 | } 26 | }); 27 | 28 | export { styles as default }; 29 | -------------------------------------------------------------------------------- /js/components/LoadingModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text, 5 | Modal, 6 | ActivityIndicator 7 | } from 'react-native'; 8 | import { SECONDARY_COLOR } from '../../styles/colors'; 9 | import LoadingStyles from './styles'; 10 | 11 | 12 | export default class LoadingModal extends Component { 13 | static propTypes = { 14 | visible: React.PropTypes.bool, 15 | message: React.PropTypes.string 16 | }; 17 | 18 | renderLoadingMessage = () => { 19 | let message = null; 20 | 21 | if (this.props.message) { 22 | message = ( 23 | 24 | {this.props.message} 25 | 26 | ); 27 | } 28 | 29 | return message; 30 | }; 31 | 32 | render() { 33 | return ( 34 | 35 | {}} 39 | > 40 | 41 | 42 | 43 | 44 | {this.renderLoadingMessage()} 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /js/components/LoadingModal/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { COLOR_WHITE } from '../../styles/colors'; 3 | 4 | const styles = StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | alignItems: 'center', 8 | justifyContent: 'center', 9 | backgroundColor: COLOR_WHITE 10 | }, 11 | innerContainer: { 12 | elevation: 5, 13 | alignSelf: 'stretch', 14 | backgroundColor: COLOR_WHITE, 15 | padding: 20, 16 | margin: 40 17 | }, 18 | spinner: { 19 | flex: 1, 20 | backgroundColor: 'transparent', 21 | alignItems: 'center', 22 | justifyContent: 'center' 23 | }, 24 | spinnerText: { 25 | textAlign: 'center', 26 | fontWeight: 'bold' 27 | } 28 | }); 29 | 30 | export { styles as default }; 31 | -------------------------------------------------------------------------------- /js/components/PaymentModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Text, 4 | View, 5 | Modal, 6 | ScrollView, 7 | TouchableOpacity 8 | } from 'react-native'; 9 | import { bindActionCreators } from 'redux'; 10 | import { connect } from 'react-redux'; 11 | import Icon from 'react-native-vector-icons/MaterialIcons'; 12 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 13 | 14 | import styles from './styles'; 15 | import { closeBuyDeviceModal } from '../../actions/modals'; 16 | import { orderDevicePostFormsSuccess } from '../../actions/forms'; 17 | 18 | const { pushRoute } = navigationActions; 19 | 20 | class PaymentModal extends Component { 21 | 22 | static propTypes = { 23 | dispatch: React.PropTypes.func, 24 | actionsCloseBuyDeviceModal: React.PropTypes.func, 25 | orderDevicePostFormsSuccess: React.PropTypes.func, 26 | visible: React.PropTypes.bool 27 | } 28 | 29 | onPress = () => { 30 | this.props.actionsCloseBuyDeviceModal(); 31 | this.props.orderDevicePostFormsSuccess(); 32 | this.props.dispatch(pushRoute({ key: 'orderDone' }, 'global')); 33 | } 34 | 35 | render() { 36 | return ( 37 | {}} 41 | > 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Select your method of payment 53 | 54 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | Pay On Delivery 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ); 74 | } 75 | } 76 | 77 | const mapDispatchToProps = (dispatch) => { 78 | return { 79 | dispatch, 80 | actionsCloseBuyDeviceModal: bindActionCreators(closeBuyDeviceModal, dispatch), 81 | orderDevicePostFormsSuccess: bindActionCreators(orderDevicePostFormsSuccess, dispatch) 82 | }; 83 | }; 84 | 85 | function mapStateToProps(state) { 86 | return {}; 87 | } 88 | 89 | export default connect(mapStateToProps, mapDispatchToProps)(PaymentModal); 90 | -------------------------------------------------------------------------------- /js/components/PaymentModal/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | SECONDARY_COLOR, 4 | PRIMARY_COLOR, 5 | COLOR_WHITE 6 | } from '../../styles/colors'; 7 | import { SCREEN_WIDTH, SCREEN_HEIGHT } from '../../styles/base'; 8 | 9 | export default StyleSheet.create({ 10 | container: { 11 | flex: 1, 12 | alignItems: 'center', 13 | justifyContent: 'center', 14 | backgroundColor: 'rgba(0,0,0,0.6)' 15 | }, 16 | button: { 17 | height: SCREEN_HEIGHT / 5.5, 18 | alignItems: 'center', 19 | justifyContent: 'center', 20 | backgroundColor: PRIMARY_COLOR, 21 | padding: 10 22 | }, 23 | buttonText: { 24 | color: COLOR_WHITE, 25 | fontSize: 22, 26 | fontFamily: 'Roboto', 27 | fontWeight: '500', 28 | textAlign: 'center', 29 | marginBottom: 5, 30 | elevation: 5 31 | }, 32 | title: { 33 | color: SECONDARY_COLOR, 34 | fontSize: 18, 35 | fontWeight: '900', 36 | textAlign: 'center', 37 | marginBottom: 5 38 | }, 39 | text: { 40 | fontSize: 15 41 | }, 42 | buttonContainer: { 43 | alignSelf: 'stretch', 44 | padding: 10 45 | }, 46 | 47 | modalContainer: { 48 | flex: 1, 49 | alignItems: 'center', 50 | alignSelf: 'stretch', 51 | backgroundColor: COLOR_WHITE, 52 | padding: 20, 53 | margin: 40, 54 | }, 55 | closeButtonContainer: { 56 | position: 'absolute', 57 | marginTop: 27, 58 | left: SCREEN_WIDTH / 1.2, 59 | zIndex: 2000, 60 | }, 61 | iconContainer: { 62 | flex: 1, 63 | alignItems: 'center', 64 | justifyContent: 'center', 65 | }, 66 | viewTitle: { 67 | width: SCREEN_WIDTH, 68 | flex: 1, 69 | flexDirection: 'column', 70 | alignItems: 'center', 71 | position: 'relative', 72 | marginBottom: 20 73 | }, 74 | closeicon: { 75 | fontSize: 40, 76 | color: PRIMARY_COLOR 77 | }, 78 | payIcon: { 79 | fontSize: 40, 80 | color: COLOR_WHITE 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /js/components/WarningModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | ScrollView, 5 | Text, 6 | Modal, 7 | } from 'react-native'; 8 | import Button from 'react-native-button'; 9 | import ModalStyles from './styles'; 10 | 11 | 12 | export default class WarningModal extends Component { 13 | static propTypes = { 14 | visible: React.PropTypes.bool.isRequired, 15 | buttons: React.PropTypes.arrayOf( 16 | React.PropTypes.shape({ 17 | text: React.PropTypes.node.isRequired, // eslint-disable-line react/no-unused-prop-types 18 | action: React.PropTypes.func.isRequired // eslint-disable-line react/no-unused-prop-types 19 | }) 20 | ).isRequired, 21 | title: React.PropTypes.string, 22 | message: React.PropTypes.string, 23 | close: React.PropTypes.func.isRequired 24 | }; 25 | 26 | executeAction = (action) => { 27 | return () => { 28 | action(); 29 | this.props.close(); 30 | }; 31 | }; 32 | 33 | renderButtons = () => { 34 | const buttons = this.props.buttons || []; 35 | let buttonsView = null; 36 | 37 | if (buttons.length === 1) { 38 | buttonsView = ( 39 | 40 | 47 | 48 | ); 49 | } else if (buttons.length === 2) { 50 | buttonsView = ( 51 | 52 | 59 | 66 | 67 | ); 68 | } 69 | 70 | return buttonsView; 71 | }; 72 | 73 | render() { 74 | return ( 75 | 76 | {}} 80 | > 81 | 82 | 85 | 86 | {this.props.title} 87 | {this.props.message} 88 | {this.renderButtons()} 89 | 90 | 91 | 92 | 93 | 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /js/components/WarningModal/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { PRIMARY_COLOR, SECONDARY_COLOR, COLOR_WHITE, COLOR_BACKDROP, COLOR_LIGHT_GRAY } from '../../styles/colors'; 3 | 4 | const ModalStyles = StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | alignItems: 'center', 8 | justifyContent: 'center', 9 | backgroundColor: COLOR_BACKDROP 10 | }, 11 | scrollView: { 12 | alignSelf: 'stretch' 13 | }, 14 | scrollViewContainer: { 15 | }, 16 | innerContainer: { 17 | elevation: 5, 18 | alignSelf: 'stretch', 19 | backgroundColor: COLOR_WHITE, 20 | padding: 20, 21 | margin: 40 22 | }, 23 | title: { 24 | color: SECONDARY_COLOR, 25 | fontSize: 24, 26 | fontWeight: 'bold', 27 | marginBottom: 10 28 | }, 29 | text: { 30 | fontSize: 15 31 | }, 32 | buttonWrapper: { 33 | justifyContent: 'flex-end', 34 | flexDirection: 'row', 35 | }, 36 | button: { 37 | fontSize: 15, 38 | color: PRIMARY_COLOR, 39 | padding: 10, 40 | fontWeight: '300' 41 | }, 42 | buttonDisabled: { 43 | fontSize: 12, 44 | color: COLOR_LIGHT_GRAY 45 | }, 46 | buttonContainer: { 47 | overflow: 'hidden', 48 | }, 49 | secondaryButton: { 50 | marginRight: 10 51 | }, 52 | }); 53 | 54 | export { ModalStyles as default }; 55 | -------------------------------------------------------------------------------- /js/components/facebookButton/facebookButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, TouchableOpacity, Text } from 'react-native'; 3 | import Icon from 'react-native-vector-icons/FontAwesome'; 4 | 5 | import styles from './styles'; 6 | 7 | export default class facebookButton extends Component { 8 | static propTypes = { 9 | onPress: React.PropTypes.func, 10 | }; 11 | render() { 12 | return ( 13 | 14 | 17 | 18 | 19 | {'Log in with Facebook'} 20 | 21 | 22 | 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /js/components/facebookButton/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { COLOR_WHITE } from '../../styles/colors'; 3 | import { SCREEN_WIDTH } from '../../styles/base'; 4 | 5 | const styles = StyleSheet.create({ 6 | buttonContainer: { 7 | alignItems: 'center', 8 | marginTop: 30, 9 | }, 10 | button: { 11 | height: 50, 12 | paddingTop: 15, 13 | backgroundColor: '#3b5998', 14 | paddingRight: 10, 15 | overflow: 'hidden', 16 | borderRadius: 4, 17 | width: SCREEN_WIDTH / 1.7, 18 | }, 19 | buttonContent: { 20 | flexDirection: 'row', 21 | }, 22 | buttonText: { 23 | flex: 1, 24 | fontSize: 16, 25 | color: COLOR_WHITE, 26 | textAlign: 'left', 27 | paddingLeft: 5 28 | }, 29 | icon: { 30 | width: 24, 31 | height: 24, 32 | marginLeft: 10, 33 | marginRight: 10 34 | }, 35 | }); 36 | 37 | export { styles as default }; 38 | -------------------------------------------------------------------------------- /js/components/signUpButton/SignUpButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, TouchableOpacity, Text } from 'react-native'; 3 | import Icon from 'react-native-vector-icons/MaterialIcons'; 4 | import { COLOR_WHITE } from '../../styles/colors'; 5 | 6 | import styles from './styles'; 7 | 8 | 9 | export default class SignUpButton extends Component { 10 | 11 | static propTypes = { 12 | onPress: React.PropTypes.func, 13 | }; 14 | render() { 15 | return ( 16 | 17 | 20 | 21 | 22 | Register with email 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /js/components/signUpButton/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Dimensions } from 'react-native'; 2 | import { COLOR_WHITE, PRIMARY_COLOR } from '../../styles/colors'; 3 | 4 | const styles = StyleSheet.create({ 5 | buttonContainer: { 6 | alignItems: 'center', 7 | marginTop: 30, 8 | }, 9 | button: { 10 | height: 50, 11 | paddingTop: 15, 12 | backgroundColor: PRIMARY_COLOR, 13 | overflow: 'hidden', 14 | borderRadius: 4, 15 | width: Dimensions.get('window').width / 1.7, 16 | }, 17 | buttonContent: { 18 | flexDirection: 'row', 19 | }, 20 | buttonText: { 21 | flex: 1, 22 | fontSize: 16, 23 | color: COLOR_WHITE, 24 | textAlign: 'left', 25 | paddingLeft: 5 26 | }, 27 | icon: { 28 | marginLeft: 10, 29 | marginRight: 10 30 | }, 31 | }); 32 | 33 | export { styles as default }; 34 | -------------------------------------------------------------------------------- /js/components/spiner/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | ActivityIndicator 5 | } from 'react-native'; 6 | 7 | import styles from './styles'; 8 | 9 | export default class Spinner extends Component { 10 | 11 | render() { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /js/components/spiner/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { PRIMARY_COLOR } from '../../styles/colors'; 3 | 4 | const styles = StyleSheet.create({ 5 | spinner: { 6 | flex: 1, 7 | backgroundColor: PRIMARY_COLOR, 8 | alignItems: 'center', 9 | justifyContent: 'center' 10 | }, 11 | }); 12 | 13 | export { styles as default }; 14 | -------------------------------------------------------------------------------- /js/constants/index.js: -------------------------------------------------------------------------------- 1 | export const AUTH_LOGIN_USER_REQUEST = 'AUTH_LOGIN_USER_REQUEST'; 2 | export const AUTH_LOGIN_USER_SUCCESS = 'AUTH_LOGIN_USER_SUCCESS'; 3 | export const AUTH_LOGIN_USER_FAILURE = 'AUTH_LOGIN_USER_FAILURE'; 4 | export const AUTH_LOGIN_USER_EXPIRED = 'AUTH_LOGIN_USER_EXPIRED'; 5 | export const AUTH_LOGIN_CLEAN_ERRORS = 'AUTH_LOGIN_CLEAN_ERRORS'; 6 | export const AUTH_LOGOUT_USER = 'AUTH_LOGOUT_USER'; 7 | export const AUTH_SAVE_USERS_INFORMATION = 'AUTH_SAVE_USERS_INFORMATION'; 8 | export const AUTH_SIGNUP_REQUEST = 'AUTH_SIGNUP_REQUEST'; 9 | export const AUTH_SIGNUP_SUCCESS = 'AUTH_SIGNUP_SUCCESS'; 10 | export const SUBMIT_SIGNUP_FORM_SAVE = 'SUBMIT_SIGNUP_FORM_SAVE'; 11 | export const SUBMIT_LOGIN_FORM_SAVE = 'SUBMIT_LOGIN_FORM_SAVE'; 12 | 13 | export const LOADING_MODAL_OPEN = 'LOADING_MODAL_OPEN'; 14 | export const LOADING_MODAL_CLOSE = 'LOADING_MODAL_CLOSE'; 15 | export const WARNING_MODAL_OPEN = 'WARNING_MODAL_OPEN'; 16 | export const WARNING_MODAL_CLOSE = 'WARNING_MODAL_CLOSE'; 17 | 18 | export const SUBMIT_ORDER_DEVICE_FORM_REQUEST = 'SUBMIT_ORDER_DEVICE_FORM_REQUEST'; 19 | export const SUBMIT_ORDER_DEVICE_FORM_SAVE = 'SUBMIT_ORDER_DEVICE_FORM_SAVE'; 20 | export const SUBMIT_ORDER_DEVICE_FORM_SUCCESS = 'SUBMIT_ORDER_DEVICE_FORM_SUCCESS'; 21 | export const SUBMIT_ORDER_DEVICE_FORM_FAILURE = 'SUBMIT_ORDER_DEVICE_FORM_FAILURE'; 22 | 23 | export const BUYDEVICE_MODAL_CLOSE = 'BUYDEVICE_MODAL_CLOSE'; 24 | export const BUYDEVICE_MODAL_OPEN = 'BUYDEVICE_MODAL_OPEN'; 25 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Activity2/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text, 5 | } from 'react-native'; 6 | import styles from './styles'; 7 | 8 | 9 | export default class Activity2 extends Component { 10 | 11 | render() { 12 | return ( 13 | 14 | 15 | Activity2 16 | 17 | 18 | 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Activity2/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | COLOR_LIGHT_GREY, 4 | PRIMARY_COLOR 5 | } from '../../../styles/colors'; 6 | 7 | export default StyleSheet.create({ 8 | container: { 9 | flex: 1, 10 | backgroundColor: COLOR_LIGHT_GREY, 11 | justifyContent: 'center', 12 | alignItems: 'center', 13 | }, 14 | h1: { 15 | color: PRIMARY_COLOR, 16 | fontSize: 20 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/CTA/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text, 5 | } from 'react-native'; 6 | import Icon from 'react-native-vector-icons/MaterialIcons'; 7 | import { connect } from 'react-redux'; 8 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 9 | 10 | import { COLOR_WHITE } from '../../../styles/colors'; 11 | import styles from './styles'; 12 | 13 | const { popRoute } = navigationActions; 14 | 15 | class Cta extends Component { 16 | 17 | static propTypes = { 18 | dispatch: React.PropTypes.func 19 | } 20 | render() { 21 | return ( 22 | 23 | { this.props.dispatch(popRoute('global')); }} 27 | title={'CTA'} 28 | /> 29 | 30 | 31 | CTA 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | } 39 | const mapDispatchToProps = (dispatch) => { 40 | return { 41 | dispatch 42 | }; 43 | }; 44 | 45 | function mapStateToProps(state) { 46 | return { 47 | }; 48 | } 49 | 50 | export default connect(mapStateToProps, mapDispatchToProps)(Cta); 51 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/CTA/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | COLOR_LIGHT_GREY, 4 | PRIMARY_COLOR 5 | } from '../../../styles/colors'; 6 | 7 | export default StyleSheet.create({ 8 | container: { 9 | flex: 1, 10 | backgroundColor: COLOR_LIGHT_GREY, 11 | }, 12 | toolbar: { 13 | height: 56, 14 | backgroundColor: PRIMARY_COLOR 15 | }, 16 | contentWrapper: { 17 | flex: 1, 18 | justifyContent: 'center', 19 | alignItems: 'center' 20 | }, 21 | h1: { 22 | color: PRIMARY_COLOR, 23 | fontSize: 20 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/Tab1/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text, 5 | TouchableOpacity 6 | } from 'react-native'; 7 | import { connect } from 'react-redux'; 8 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 9 | 10 | import styles from './styles'; 11 | 12 | const { pushRoute } = navigationActions; 13 | 14 | class Tab1 extends Component { 15 | 16 | static propTypes = { 17 | dispatch: React.PropTypes.func 18 | }; 19 | 20 | static contextTypes = { 21 | analytics: React.PropTypes.object 22 | }; 23 | 24 | componentDidMount() { 25 | this.context.analytics.trackScreenView('Tab1'); 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 | 32 | { this.props.dispatch(pushRoute({ key: 'cta' }, 'global')); }} 33 | style={styles.button} 34 | > 35 | 36 | CTA 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | } 44 | 45 | const mapDispatchToProps = (dispatch) => { 46 | return { 47 | dispatch 48 | }; 49 | }; 50 | 51 | function mapStateToProps(state) { 52 | return { 53 | }; 54 | } 55 | 56 | export default connect(mapStateToProps, mapDispatchToProps)(Tab1); 57 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/Tab1/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | COLOR_LIGHT_GREY, 4 | PRIMARY_COLOR, 5 | COLOR_WHITE 6 | } from '../../../../../styles/colors'; 7 | import { SCREEN_WIDTH } from '../../../../../styles/base'; 8 | 9 | export default StyleSheet.create({ 10 | container: { 11 | flex: 1, 12 | backgroundColor: COLOR_LIGHT_GREY, 13 | justifyContent: 'center', 14 | alignItems: 'center', 15 | }, 16 | buttonWrapper: { 17 | width: SCREEN_WIDTH, 18 | padding: 16 19 | }, 20 | button: { 21 | backgroundColor: PRIMARY_COLOR, 22 | padding: 20, 23 | borderRadius: 6, 24 | alignItems: 'center' 25 | }, 26 | h1: { 27 | color: COLOR_WHITE, 28 | fontSize: 20 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/Tab2/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text 5 | } from 'react-native'; 6 | import { connect } from 'react-redux'; 7 | 8 | import styles from './styles'; 9 | 10 | 11 | class Tab2 extends Component { 12 | 13 | static propTypes = { 14 | dispatch: React.PropTypes.func 15 | }; 16 | 17 | static contextTypes = { 18 | analytics: React.PropTypes.object 19 | }; 20 | 21 | componentDidMount() { 22 | this.context.analytics.trackScreenView('Tab2'); 23 | } 24 | 25 | render() { 26 | return ( 27 | 28 | 29 | Tab2 30 | 31 | 32 | ); 33 | } 34 | } 35 | 36 | const mapDispatchToProps = (dispatch) => { 37 | return { 38 | dispatch 39 | }; 40 | }; 41 | 42 | function mapStateToProps(state) { 43 | return { 44 | }; 45 | } 46 | 47 | export default connect(mapStateToProps, mapDispatchToProps)(Tab2); 48 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/Tab2/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | COLOR_LIGHT_GRAY, 4 | PRIMARY_COLOR 5 | } from '../../../../../styles/colors'; 6 | 7 | export default StyleSheet.create({ 8 | container: { 9 | flex: 1, 10 | backgroundColor: COLOR_LIGHT_GRAY, 11 | justifyContent: 'center', 12 | borderRadius: 6, 13 | alignItems: 'center', 14 | paddingTop: 10, 15 | paddingBottom: 10, 16 | }, 17 | h1: { 18 | color: PRIMARY_COLOR, 19 | fontSize: 20 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/Tab3/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Text, 4 | View, 5 | TouchableOpacity, 6 | ScrollView 7 | } from 'react-native'; 8 | import { bindActionCreators } from 'redux'; 9 | import { connect } from 'react-redux'; 10 | import t from 'tcomb-form-native'; 11 | 12 | import PaymentModal from '../../../../../components/PaymentModal'; 13 | import Spinner from '../../../../../components/spiner'; 14 | import { orderDeviceValidatePostForms, orderDeviceSaveForms } from '../../../../../actions/forms'; 15 | import { openBuyDeviceModal } from '../../../../../actions/modals'; 16 | import { validateEmail, PhoneNumber } from '../../../../../utils/validators'; 17 | import phoneNumberTransformer from '../../../../../utils/transformers'; 18 | import styles from './styles'; 19 | import formStyles from '../../../../../styles/form'; 20 | import { 21 | SECONDARY_COLOR, 22 | } from '../../../../../styles/colors'; 23 | 24 | const Form = t.form.Form; 25 | const emailForm = { 26 | email: validateEmail 27 | }; 28 | const generalFormFields = { 29 | address: t.String, 30 | phone_number: PhoneNumber, 31 | }; 32 | const options = { 33 | stylesheet: formStyles, 34 | fields: { 35 | address: { 36 | error: 'This field is required', 37 | label: 'Address', 38 | underlineColorAndroid: SECONDARY_COLOR, 39 | }, 40 | phone_number: { 41 | transformer: phoneNumberTransformer, 42 | error: 'International phone number format is required', 43 | label: 'Contact Number', 44 | underlineColorAndroid: SECONDARY_COLOR, 45 | }, 46 | email: { 47 | error: 'This field should be a valid email', 48 | underlineColorAndroid: SECONDARY_COLOR, 49 | } 50 | } 51 | }; 52 | 53 | class Tab3 extends Component { 54 | 55 | static propTypes = { 56 | actionsOrderDeviceSaveForms: React.PropTypes.func, 57 | actionsOrderDeviceValidatePostForms: React.PropTypes.func, 58 | actionsOpenBuyDeviceModal: React.PropTypes.func, 59 | modalVisible: React.PropTypes.bool, 60 | index: React.PropTypes.number, 61 | auth: React.PropTypes.shape({ 62 | token: React.PropTypes.string, 63 | email: React.PropTypes.string, 64 | }), 65 | form: React.PropTypes.shape({ 66 | isFetching: React.PropTypes.bool.isRequired, 67 | formData: React.PropTypes.object.isRequired, 68 | errorCode: React.PropTypes.number, // eslint-disable-line 69 | errorObject: React.PropTypes.shape({ 70 | non_field_errors: React.PropTypes.arrayOf(React.PropTypes.string) // eslint-disable-line 71 | }), 72 | }), 73 | }; 74 | 75 | static contextTypes = { 76 | analytics: React.PropTypes.object 77 | }; 78 | 79 | constructor(props) { 80 | super(props); 81 | this.state = { 82 | formOptions: options, 83 | modalVisible: this.props.modalVisible, 84 | orderForm: t.struct(this.calcForm()), 85 | }; 86 | } 87 | componentDidMount() { 88 | this.context.analytics.trackScreenView('Tab3'); 89 | } 90 | 91 | componentWillReceiveProps(nextProps) { 92 | const errorState = {}; 93 | Object.keys(this.calcForm()).forEach((item) => { 94 | errorState[item] = { 95 | hasError: { $set: false }, 96 | error: { $set: '' } 97 | }; 98 | }); 99 | if (nextProps.form.errorObject) { 100 | Object.keys(nextProps.form.errorObject).forEach((item) => { 101 | errorState[item] = { 102 | hasError: { $set: true }, 103 | error: { $set: nextProps.form.errorObject[item][0] } 104 | }; 105 | }); 106 | } 107 | const newFormOptions = t.update(this.state.formOptions, { fields: errorState }); 108 | this.setState({ 109 | formOptions: newFormOptions, 110 | modalVisible: nextProps.modalVisible 111 | }); 112 | } 113 | 114 | calcForm = () => { 115 | let email = {}; 116 | if (!this.props.auth.email) { 117 | email = emailForm; 118 | } 119 | const newForm = { ...email, ...generalFormFields }; 120 | return newForm; 121 | } 122 | 123 | submit = () => { 124 | this.form.validate(); 125 | const value = this.form.getValue(); 126 | if (value) { 127 | this.props.actionsOpenBuyDeviceModal(); 128 | this.props.actionsOrderDeviceSaveForms(value); 129 | } 130 | } 131 | 132 | processPayment = (type) => { 133 | const values = Object.assign({}, this.props.form.formData, { 134 | payment: type 135 | }); 136 | if (this.props.auth.email) { 137 | this.props.actionsOrderDeviceValidatePostForms(this.props.auth.token, values, 138 | this.props.auth.email, this.props.index); 139 | } else { 140 | this.props.actionsOrderDeviceValidatePostForms(this.props.auth.token, values, 141 | values.email, this.props.index); 142 | } 143 | } 144 | 145 | render() { 146 | let renderScene; 147 | if (this.props.form.isFetching) { 148 | renderScene = ; 149 | } else { 150 | renderScene = ( 151 | 152 | 153 | 154 | 155 | 156 | Order Your Cool Device Today! 157 | 158 | 159 | USD 10.99 160 | 161 | 162 | 163 |

{ this.form = c; }} 164 | type={this.state.orderForm} 165 | value={this.props.form.formData} 166 | options={this.state.formOptions} 167 | /> 168 | 169 | 170 | { this.submit(); }} 171 | style={styles.scheduleButton} 172 | > 173 | 174 | Select Payment Type 175 | 176 | 177 | 178 | 179 | 180 | ); 181 | } 182 | return renderScene; 183 | } 184 | } 185 | 186 | function mapDispatchToProps(dispatch) { 187 | return { 188 | dispatch, 189 | actionsOrderDeviceValidatePostForms: bindActionCreators(orderDeviceValidatePostForms, dispatch), 190 | actionsOrderDeviceSaveForms: bindActionCreators(orderDeviceSaveForms, dispatch), 191 | actionsOpenBuyDeviceModal: bindActionCreators(openBuyDeviceModal, dispatch), 192 | }; 193 | } 194 | 195 | function mapStateToProps(state) { 196 | return { 197 | auth: state.auth, 198 | form: state.form, 199 | index: state.globalNavigation.index, 200 | modalVisible: state.modals.BuyDevicevisible 201 | }; 202 | } 203 | export default connect(mapStateToProps, mapDispatchToProps)(Tab3); 204 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/Tab3/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | COLOR_WHITE, 4 | PRIMARY_COLOR, 5 | SECONDARY_COLOR, 6 | } from '../../../../../styles/colors'; 7 | import { SCREEN_WIDTH } from '../../../../../styles/base'; 8 | 9 | export default StyleSheet.create({ 10 | container: { 11 | flex: 1, 12 | backgroundColor: COLOR_WHITE, 13 | justifyContent: 'center', 14 | }, 15 | iconContainer: { 16 | flex: 1, 17 | alignItems: 'center', 18 | paddingTop: 10, 19 | paddingBottom: 10, 20 | }, 21 | icon: { 22 | width: SCREEN_WIDTH, 23 | height: 120, 24 | flex: 1, 25 | resizeMode: 'contain' 26 | }, 27 | subTitle: { 28 | fontSize: 17, 29 | color: PRIMARY_COLOR, 30 | fontFamily: 'Roboto', 31 | fontWeight: '300', 32 | marginTop: 10 33 | }, 34 | priceColor: { 35 | marginTop: 5, 36 | color: SECONDARY_COLOR, 37 | fontSize: 25, 38 | fontFamily: 'Roboto', 39 | fontWeight: '800' 40 | }, 41 | scheduleButtonContainer: { 42 | alignItems: 'center', 43 | }, 44 | scheduleButton: { 45 | backgroundColor: PRIMARY_COLOR, 46 | padding: 20, 47 | borderRadius: 6, 48 | marginBottom: 20, 49 | width: SCREEN_WIDTH - 40, 50 | alignItems: 'center' 51 | 52 | }, 53 | scheduleButtonTitle: { 54 | fontSize: 18, 55 | color: COLOR_WHITE, 56 | fontFamily: 'Roboto', 57 | fontWeight: '500', 58 | }, 59 | formContainer: { 60 | padding: 25, 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View } from 'react-native'; 3 | import { TabViewAnimated, TabBarTop } from 'react-native-tab-view'; 4 | import { connect } from 'react-redux'; 5 | import Icon from 'react-native-vector-icons/MaterialIcons'; 6 | 7 | import Tab3 from './Tab3'; 8 | import Tab2 from './Tab2'; 9 | import Tab1 from './Tab1'; 10 | import styles from './styles'; 11 | 12 | 13 | class Tabs extends Component { 14 | 15 | static title = 'Scrollable top bar'; 16 | static appbarElevation = 0; 17 | 18 | static propTypes = { 19 | style: View.propTypes.style, 20 | }; 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | index: 0, 25 | routes: [ 26 | { key: 'Tab1', icon: 'looks-one' }, 27 | { key: 'Tab2', icon: 'looks-two' }, 28 | { key: 'Tab3', icon: 'looks-3' } 29 | ], 30 | }; 31 | } 32 | 33 | handleChangeTab = (index) => { 34 | this.setState({ 35 | index, 36 | }); 37 | }; 38 | 39 | 40 | renderIcon = ({ route }) => { 41 | return ( 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | renderHeader = (props) => { 49 | return ( 50 | 56 | ); 57 | }; 58 | 59 | renderScene = ({ route }) => { 60 | switch (route.key) { 61 | case 'Tab1': 62 | return ; 63 | case 'Tab2': 64 | return (); 65 | case 'Tab3': 66 | return ; 67 | default: 68 | return null; 69 | } 70 | }; 71 | 72 | render() { 73 | return ( 74 | 80 | ); 81 | } 82 | } 83 | 84 | function mapDispatchToProps(dispatch) { 85 | return { 86 | dispatch, 87 | }; 88 | } 89 | 90 | function mapStateToProps(state) { 91 | return { 92 | }; 93 | } 94 | 95 | export default connect(mapStateToProps, mapDispatchToProps)(Tabs); 96 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/Tabs/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | PRIMARY_COLOR, 4 | COLOR_WHITE 5 | } from '../../../../styles/colors'; 6 | 7 | export default StyleSheet.create({ 8 | container: { 9 | flex: 1, 10 | }, 11 | tabbar: { 12 | backgroundColor: PRIMARY_COLOR, 13 | }, 14 | page: { 15 | alignItems: 'center', 16 | justifyContent: 'center', 17 | }, 18 | indicator: { 19 | backgroundColor: '#fff', 20 | }, 21 | label: { 22 | color: '#fff', 23 | fontWeight: '400', 24 | }, 25 | icon: { 26 | fontSize: 36, 27 | color: COLOR_WHITE 28 | }, 29 | iconContainer: { 30 | flex: 1, 31 | alignItems: 'center', 32 | justifyContent: 'center', 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Alert, 4 | Linking, 5 | } from 'react-native'; 6 | import { connect } from 'react-redux'; 7 | 8 | import Tabs from './Tabs'; 9 | 10 | 11 | class Home extends Component { 12 | 13 | static propTypes = { 14 | outdatedApp: React.PropTypes.bool 15 | }; 16 | 17 | static contextTypes = { 18 | analytics: React.PropTypes.object 19 | }; 20 | 21 | componentDidMount() { 22 | this.context.analytics.trackScreenView('Home'); 23 | } 24 | 25 | componentWillReceiveProps(nextProps) { 26 | if (nextProps.outdatedApp) { 27 | Alert.alert( 28 | 'New app is available', 29 | 'We\'ve added new features to improve the quality of your application!', 30 | [ 31 | { text: 'Remind later' }, 32 | { text: 'Update', onPress: this.updateApp }, 33 | ] 34 | ); 35 | } 36 | } 37 | 38 | updateApp = () => { 39 | Linking.openURL('market://details?id=com.seedstarsbase'); 40 | } 41 | 42 | 43 | render() { 44 | return ( 45 | 46 | ); 47 | } 48 | } 49 | 50 | 51 | const mapDispatchToProps = (dispatch) => { 52 | return { 53 | dispatch 54 | }; 55 | }; 56 | 57 | const mapStateToProps = (state) => { 58 | return { 59 | outdatedApp: state.auth.outdated, 60 | }; 61 | }; 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(Home); 64 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/OrderDone/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | View, 4 | Text, 5 | } from 'react-native'; 6 | import Icon from 'react-native-vector-icons/MaterialIcons'; 7 | import { connect } from 'react-redux'; 8 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 9 | 10 | import { COLOR_WHITE } from '../../../styles/colors'; 11 | import styles from './styles'; 12 | 13 | const { popRoute } = navigationActions; 14 | 15 | class OrderDone extends Component { 16 | 17 | static propTypes = { 18 | dispatch: React.PropTypes.func 19 | } 20 | render() { 21 | return ( 22 | 23 | { this.props.dispatch(popRoute('global')); }} 27 | title={'Order Done'} 28 | /> 29 | 30 | 31 | 32 | Order Done 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | } 40 | const mapDispatchToProps = (dispatch) => { 41 | return { 42 | dispatch 43 | }; 44 | }; 45 | 46 | function mapStateToProps(state) { 47 | return { 48 | }; 49 | } 50 | 51 | export default connect(mapStateToProps, mapDispatchToProps)(OrderDone); 52 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/OrderDone/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | COLOR_LIGHT_GREY, 4 | PRIMARY_COLOR 5 | } from '../../../styles/colors'; 6 | 7 | export default StyleSheet.create({ 8 | container: { 9 | flex: 1, 10 | backgroundColor: COLOR_LIGHT_GREY, 11 | }, 12 | toolbar: { 13 | height: 56, 14 | backgroundColor: PRIMARY_COLOR 15 | }, 16 | contentWrapper: { 17 | flex: 1, 18 | justifyContent: 'center', 19 | alignItems: 'center' 20 | }, 21 | h1: { 22 | color: PRIMARY_COLOR, 23 | fontSize: 20 24 | }, 25 | icon: { 26 | fontSize: 60, 27 | color: PRIMARY_COLOR, 28 | marginBottom: 10 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | View, 3 | Text, 4 | TouchableHighlight, 5 | DrawerLayoutAndroid, 6 | Image 7 | } from 'react-native'; 8 | import React, { Component } from 'react'; 9 | import { connect } from 'react-redux'; 10 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 11 | import Communications from 'react-native-communications'; 12 | import Icon from 'react-native-vector-icons/MaterialIcons'; 13 | import Storage from 'react-native-simple-store'; 14 | import { styles, menuItemStyles } from './styles'; 15 | import Home from './Home'; 16 | import Activity2 from './Activity2'; 17 | import { 18 | COLOR_LIGHT_GREY, 19 | COLOR_DARK_GREY, 20 | COLOR_WHITE 21 | } from '../../styles/colors'; 22 | import noAvatar from '../../images/no_avatar.png'; 23 | 24 | const { jumpTo, reset } = navigationActions; 25 | 26 | class ApplicationTabs extends Component { 27 | 28 | static propTypes = { 29 | authFirstName: React.PropTypes.string, 30 | authLastName: React.PropTypes.string, 31 | authEmail: React.PropTypes.string, 32 | authFacebookUID: React.PropTypes.string, 33 | navigationTabsIndex: React.PropTypes.number, 34 | navigationTabsRoutes: React.PropTypes.arrayOf(React.PropTypes.object), 35 | navigationTabsText: React.PropTypes.arrayOf(React.PropTypes.object), 36 | navigationTabskey: React.PropTypes.string, 37 | dispatch: React.PropTypes.func, 38 | }; 39 | 40 | sendFeedbackEmail = () => { 41 | Communications.email(['feedback@example.com'], null, null, 'AutoService - Feedback', null); 42 | } 43 | logout = () => { 44 | Storage.delete('user'); 45 | this.props.dispatch(reset([{ 46 | key: 'login', 47 | index: 0 48 | }], 'global')); 49 | } 50 | 51 | calcDrawerImage() { 52 | let image; 53 | if (!isNaN(this.props.authFacebookUID)) { 54 | image = ( 55 | 58 | ); 59 | } else { 60 | image = ( 61 | 64 | ); 65 | } 66 | return ( 67 | image 68 | ); 69 | } 70 | renderTabContent = (tab) => { 71 | let activeTab; 72 | 73 | switch (tab.key) { 74 | case 'activity2': 75 | activeTab = ; 76 | break; 77 | case 'home': 78 | default: 79 | activeTab = ; 80 | } 81 | 82 | return activeTab; 83 | } 84 | renderApp() { 85 | const selectedTab = this.props.navigationTabsRoutes[this.props.navigationTabsIndex]; 86 | 87 | return ( 88 | 89 | { this.drawer.openDrawer(); }} 93 | title={selectedTab.title} 94 | /> 95 | {this.renderTabContent(selectedTab)} 96 | 97 | ); 98 | } 99 | 100 | render() { 101 | const onNavigate = (action) => { 102 | this.drawer.closeDrawer(); 103 | this.props.dispatch(action); 104 | }; 105 | 106 | const navigationView = () => { 107 | return ( 108 | 109 | 110 | { this.calcDrawerImage() } 111 | 112 | {this.props.authFirstName} {this.props.authLastName} 113 | 114 | 115 | {this.props.authEmail} 116 | 117 | 118 | {this.props.navigationTabsRoutes.map((t, i) => { 119 | return ( 120 | { onNavigate(jumpTo(i, this.props.navigationTabskey)); }} 121 | key={t.key} 122 | underlayColor={COLOR_DARK_GREY} 123 | > 124 | 125 | 126 | 127 | {t.title} 128 | 129 | 130 | 131 | ); 132 | })} 133 | {this.props.navigationTabsText.map((t, i) => { 134 | return ( 135 | 138 | 139 | {t.title} 140 | 141 | 142 | ); 143 | })} 144 | { this.sendFeedbackEmail(); }} 145 | underlayColor={COLOR_DARK_GREY} 146 | > 147 | 148 | 149 | 150 | Feedback 151 | 152 | 153 | 154 | { this.logout(); }} 155 | underlayColor={COLOR_DARK_GREY} 156 | > 157 | 158 | 159 | 160 | Logout 161 | 162 | 163 | 164 | 165 | ); 166 | }; 167 | 168 | return ( 169 | { this.drawer = c; }} 170 | drawerWidth={this.width - 56 <= 320 ? this.width - 56 : 320} 171 | drawerPosition={DrawerLayoutAndroid.positions.Left} 172 | renderNavigationView={navigationView} 173 | drawerBackgroundColor={COLOR_LIGHT_GREY} 174 | > 175 | {this.renderApp()} 176 | 177 | ); 178 | } 179 | 180 | } 181 | 182 | 183 | function mapDispatchToProps(dispatch) { 184 | return { 185 | dispatch, 186 | }; 187 | } 188 | 189 | function mapStateToProps(state) { 190 | return { 191 | navigationTabsIndex: state.tabs.index, 192 | navigationTabsRoutes: state.tabs.routes, 193 | navigationTabsText: state.tabs.text, 194 | navigationTabskey: state.tabs.key, 195 | authFirstName: state.auth.firstName, 196 | authLastName: state.auth.lastName, 197 | authEmail: state.auth.email, 198 | authFacebookUID: state.auth.facebook_uid 199 | }; 200 | } 201 | export default connect(mapStateToProps, mapDispatchToProps)(ApplicationTabs); 202 | -------------------------------------------------------------------------------- /js/containers/ApplicationTabs/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | COLOR_WHITE, 4 | COLOR_SILVER_LIGHT, 5 | COLOR_SILVER_DARK, 6 | PRIMARY_COLOR, 7 | COLOR_LIGHT_GREY, 8 | } from '../../styles/colors'; 9 | import { SCREEN_WIDTH } from '../../styles/base'; 10 | 11 | export const styles = StyleSheet.create({ 12 | container: { 13 | flex: 1, 14 | backgroundColor: COLOR_LIGHT_GREY, 15 | }, 16 | drawer: { 17 | flex: 1, 18 | backgroundColor: COLOR_LIGHT_GREY 19 | }, 20 | drawerHeader: { 21 | backgroundColor: PRIMARY_COLOR, 22 | height: 200, 23 | paddingBottom: 20, 24 | paddingLeft: 16, 25 | alignItems: 'center', 26 | marginBottom: 10 27 | }, 28 | userFullName: { 29 | fontFamily: 'Roboto', 30 | fontWeight: '300', 31 | fontSize: 22, 32 | color: COLOR_WHITE, 33 | marginTop: 10 34 | }, 35 | userEmail: { 36 | fontFamily: 'Roboto', 37 | fontWeight: '100', 38 | fontSize: 16, 39 | color: COLOR_WHITE, 40 | marginTop: 3 41 | }, 42 | toolbar: { 43 | height: 56, 44 | backgroundColor: PRIMARY_COLOR 45 | }, 46 | facebookPhoto: { 47 | marginTop: 20, 48 | width: SCREEN_WIDTH / 5, 49 | height: SCREEN_WIDTH / 5, 50 | borderRadius: 200, 51 | borderWidth: 2, 52 | borderColor: COLOR_WHITE 53 | }, 54 | }); 55 | 56 | export const menuItemStyles = StyleSheet.create({ 57 | container: { 58 | paddingTop: 10, 59 | paddingBottom: 10, 60 | flexDirection: 'row', 61 | alignItems: 'center' 62 | }, 63 | divider: { 64 | borderTopWidth: 1, 65 | borderTopColor: COLOR_SILVER_LIGHT 66 | }, 67 | icon: { 68 | marginLeft: 16, 69 | fontSize: 25, 70 | color: COLOR_SILVER_DARK 71 | }, 72 | title: { 73 | fontFamily: 'Roboto', 74 | fontWeight: '300', 75 | fontSize: 18, 76 | marginLeft: 10, 77 | color: COLOR_SILVER_DARK 78 | }, 79 | selected: { 80 | color: PRIMARY_COLOR 81 | }, 82 | itemIcon: { 83 | fontSize: 24, 84 | marginRight: 10, 85 | marginLeft: 16, 86 | color: PRIMARY_COLOR 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /js/containers/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Text, Image, View, ActivityIndicator, Alert } from 'react-native'; 3 | import { LoginManager, AccessToken } from 'react-native-fbsdk'; 4 | import { connect } from 'react-redux'; 5 | import { bindActionCreators } from 'redux'; 6 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 7 | 8 | import ICON from '../../images/icon.png'; 9 | import styles from './styles'; 10 | import FacebookButton from '../../components/facebookButton/facebookButton'; 11 | import SignUpButton from '../../components/signUpButton/SignUpButton'; 12 | import BottomButton from '../../components/BottomButton/bottomButton'; 13 | import { loginFacebook, cleanErrors } from '../../actions/auth'; 14 | 15 | 16 | const { pushRoute } = navigationActions; 17 | 18 | class LoginView extends Component { 19 | static propTypes = { 20 | dispatch: React.PropTypes.func, 21 | loginFacebook: React.PropTypes.func, 22 | cleanErrors: React.PropTypes.func, 23 | errorCode: React.PropTypes.shape(), 24 | authenticationExpired: React.PropTypes.bool, 25 | isFetching: React.PropTypes.bool 26 | }; 27 | 28 | static contextTypes = { 29 | analytics: React.PropTypes.object 30 | }; 31 | 32 | constructor(props) { 33 | super(props); 34 | this.state = { 35 | loginState: this.props.isFetching, 36 | }; 37 | } 38 | 39 | componentDidMount() { 40 | this.context.analytics.trackScreenView('Login'); 41 | } 42 | 43 | componentWillReceiveProps(nextProps) { 44 | this.setState({ loginState: nextProps.isFetching }); 45 | } 46 | 47 | getRemoteFacebookAccessToken = () => { 48 | return AccessToken.getCurrentAccessToken().then((data) => { 49 | return data && data.accessToken ? data.accessToken.toString() : null; 50 | }); 51 | }; 52 | 53 | login = () => { 54 | LoginManager.logInWithReadPermissions(['email', 'public_profile']) 55 | .then(() => { 56 | this.getRemoteFacebookAccessToken() 57 | .then((token) => { 58 | if (token) { 59 | this.props.loginFacebook(token); 60 | } else { 61 | this.setState({ loginState: false }); 62 | } 63 | }); 64 | }); 65 | }; 66 | 67 | signup = () => { 68 | this.props.dispatch(pushRoute({ key: 'register' }, 'global')); 69 | }; 70 | 71 | loginEmail = () => { 72 | this.props.dispatch(pushRoute({ key: 'loginEmail' }, 'global')); 73 | } 74 | 75 | render() { 76 | const renderSence = ( 77 | 78 | 79 | 80 | 81 | 82 | My Store Foo 83 | 84 | {!this.state.loginState ? 85 | 86 | 87 | 88 | 89 | 90 | 91 | 96 | 97 | 98 | : 99 | 100 | 101 | Logging in... 102 | 103 | } 104 | 105 | ); 106 | if (this.props.errorCode === 401 && this.props.authenticationExpired) { 107 | Alert.alert('Authentication Error', 'You session is not valid. Please Login again'); 108 | this.props.cleanErrors(); 109 | } 110 | return renderSence; 111 | } 112 | } 113 | function mapDispatchToProps(dispatch) { 114 | return { 115 | dispatch, 116 | loginFacebook: bindActionCreators(loginFacebook, dispatch), 117 | cleanErrors: bindActionCreators(cleanErrors, dispatch) 118 | }; 119 | } 120 | 121 | function mapStateToProps(state) { 122 | return { 123 | navigation: state.globalNavigation, 124 | errorCode: state.auth.errorCode, 125 | isFetching: state.auth.isFetching, 126 | authenticationExpired: state.auth.authenticationExpired, 127 | }; 128 | } 129 | 130 | export default connect(mapStateToProps, mapDispatchToProps)(LoginView); 131 | -------------------------------------------------------------------------------- /js/containers/Login/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | import { COLOR_WHITE, PRIMARY_COLOR } from '../../styles/colors'; 4 | import { SCREEN_WIDTH } from '../../styles/base'; 5 | 6 | const styles = StyleSheet.create({ 7 | BackgroundImage: { 8 | flex: 1, 9 | backgroundColor: COLOR_WHITE, 10 | width: null, 11 | height: null, 12 | }, 13 | container: { 14 | flex: 1, 15 | backgroundColor: COLOR_WHITE, 16 | paddingTop: 50 17 | }, 18 | iconContainer: { 19 | alignItems: 'center' 20 | }, 21 | icon: { 22 | width: SCREEN_WIDTH / 4, 23 | height: SCREEN_WIDTH / 4 24 | }, 25 | title: { 26 | color: PRIMARY_COLOR, 27 | fontSize: 25, 28 | textAlign: 'center', 29 | margin: 15 30 | }, 31 | buttonContainer: { 32 | flex: 1, 33 | }, 34 | loginButton: { 35 | color: '#666666', 36 | fontSize: 15 37 | }, 38 | bottom: { 39 | position: 'absolute', 40 | bottom: 0, 41 | }, 42 | activityIndicatorContainer: { 43 | alignItems: 'center' 44 | }, 45 | h1: { 46 | marginTop: 10, 47 | color: COLOR_WHITE 48 | } 49 | }); 50 | 51 | export { styles as default }; 52 | -------------------------------------------------------------------------------- /js/containers/LoginEmail/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, Alert, TouchableOpacity, Text, ScrollView } from 'react-native'; 3 | import t from 'tcomb-form-native'; 4 | import { connect } from 'react-redux'; 5 | import { bindActionCreators } from 'redux'; 6 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 7 | import Icon from 'react-native-vector-icons/MaterialIcons'; 8 | 9 | import styles from './styles'; 10 | import Spinner from '../../components/spiner'; 11 | import { validateEmail, validatePassword } from '../../utils/validators'; 12 | import formStyles from '../../styles/form-white'; 13 | import { COLOR_WHITE } from '../../styles/colors'; 14 | import { loginUser, loginSaveForms, cleanErrors } from '../../actions/auth'; 15 | 16 | const { popRoute } = navigationActions; 17 | const Form = t.form.Form; 18 | 19 | const generalFormFields = { 20 | email: validateEmail, 21 | password: validatePassword, 22 | }; 23 | 24 | const options = { 25 | stylesheet: formStyles, 26 | fields: { 27 | email: { 28 | error: 'This field should be a valid email', 29 | underlineColorAndroid: COLOR_WHITE, 30 | keyboardType: 'email-address' 31 | }, 32 | password: { 33 | password: true, 34 | secureTextEntry: true, 35 | error: 'This field is required', 36 | placeholder: 'Must have at least 6 characters', 37 | placeholderTextColor: COLOR_WHITE 38 | }, 39 | } 40 | }; 41 | 42 | const registerForm = t.struct(generalFormFields); 43 | 44 | 45 | class LoginEmail extends Component { 46 | 47 | static propTypes = { 48 | isFetching: React.PropTypes.bool, 49 | dispatch: React.PropTypes.func, 50 | actionsLoginUser: React.PropTypes.func, 51 | actionsLoginSaveForms: React.PropTypes.func, 52 | cleanErrors: React.PropTypes.func, 53 | formData: React.PropTypes.object, // eslint-disable-line react/forbid-prop-types 54 | }; 55 | 56 | static contextTypes = { 57 | analytics: React.PropTypes.object 58 | }; 59 | 60 | constructor(props) { 61 | super(props); 62 | this.state = { 63 | formOptions: options, 64 | }; 65 | } 66 | 67 | componentDidMount() { 68 | this.context.analytics.trackScreenView('Login with Email'); 69 | } 70 | 71 | componentWillReceiveProps(nextProps) { 72 | const errorState = {}; 73 | 74 | Object.keys(generalFormFields).forEach((item) => { 75 | errorState[item] = { 76 | hasError: { $set: false }, 77 | error: { $set: '' } 78 | }; 79 | }); 80 | if (nextProps.auth.errorCode >= 400 && typeof nextProps.auth.errorObject.non_field_errors !== 'undefined') { 81 | Alert.alert('Server Error', nextProps.auth.errorObject.non_field_errors[0]); 82 | this.props.cleanErrors(); 83 | } else { 84 | if (nextProps.auth.errorObject) { 85 | Object.keys(nextProps.auth.errorObject).forEach((item) => { 86 | errorState[item] = { 87 | hasError: { $set: true }, 88 | error: { $set: nextProps.auth.errorObject[item][0] } 89 | }; 90 | }); 91 | } 92 | 93 | const newFormOptions = t.update(this.state.formOptions, { fields: errorState }); 94 | this.setState({ 95 | formOptions: newFormOptions 96 | }); 97 | } 98 | } 99 | 100 | submit = () => { 101 | this.form.validate(); 102 | const value = this.form.getValue(); 103 | 104 | if (value) { 105 | this.props.actionsLoginUser(value); 106 | this.props.actionsLoginSaveForms(value); 107 | } 108 | } 109 | 110 | render() { 111 | let renderScene; 112 | if (this.props.isFetching) { 113 | renderScene = ; 114 | } else { 115 | renderScene = ( 116 | 117 | { this.props.dispatch(popRoute('global')); }} 121 | /> 122 | 123 | 124 | { this.form = c; }} 125 | type={registerForm} 126 | value={this.props.formData} 127 | options={this.state.formOptions} 128 | /> 129 | { this.submit(); }} 130 | style={styles.registerButton} 131 | > 132 | 133 | Login 134 | 135 | 136 | 137 | 138 | 139 | ); 140 | } 141 | 142 | return renderScene; 143 | } 144 | } 145 | 146 | function mapDispatchToProps(dispatch) { 147 | return { 148 | dispatch, 149 | actionsLoginUser: bindActionCreators(loginUser, dispatch), 150 | actionsLoginSaveForms: bindActionCreators(loginSaveForms, dispatch), 151 | cleanErrors: bindActionCreators(cleanErrors, dispatch) 152 | 153 | 154 | }; 155 | } 156 | 157 | function mapStateToProps(state) { 158 | return { 159 | isFetching: state.auth.isAuthenticating, 160 | formData: state.auth.formData, 161 | // errorCode: state.auth.errorCode, 162 | // errorObject: state.auth.errorObject, 163 | navigation: state.globalNavigation, 164 | auth: state.auth, 165 | }; 166 | } 167 | 168 | export default connect(mapStateToProps, mapDispatchToProps)(LoginEmail); 169 | -------------------------------------------------------------------------------- /js/containers/LoginEmail/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { PRIMARY_COLOR, COLOR_WHITE } from '../../styles/colors'; 3 | 4 | const styles = StyleSheet.create({ 5 | scrollView: { 6 | flex: 1, 7 | }, 8 | toolbar: { 9 | height: 56, 10 | backgroundColor: PRIMARY_COLOR 11 | }, 12 | container: { 13 | flex: 1, 14 | backgroundColor: PRIMARY_COLOR, 15 | }, 16 | contentContaier: { 17 | padding: 30, 18 | }, 19 | registerButton: { 20 | backgroundColor: COLOR_WHITE, 21 | padding: 20, 22 | borderRadius: 6, 23 | marginBottom: 20, 24 | alignItems: 'center' 25 | }, 26 | registerButtonTitle: { 27 | fontSize: 18, 28 | color: PRIMARY_COLOR, 29 | } 30 | }); 31 | 32 | export { styles as default }; 33 | -------------------------------------------------------------------------------- /js/containers/MainNavigation/index.js: -------------------------------------------------------------------------------- 1 | import { BackAndroid, View, NavigationExperimental } from 'react-native'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 5 | import { bindActionCreators } from 'redux'; 6 | 7 | import ApplicationTabs from '../ApplicationTabs'; 8 | import Splash from '../Splash'; 9 | import Login from '../Login'; 10 | import Register from '../Register'; 11 | import LoginEmail from '../LoginEmail'; 12 | import Cta from '../ApplicationTabs/CTA'; 13 | import OrderDone from '../ApplicationTabs/OrderDone'; 14 | import LoadingModal from '../../components/LoadingModal'; 15 | import WarningModal from '../../components/WarningModal'; 16 | import { closeWarningModal } from '../../actions/modals'; 17 | 18 | const { CardStack: NavigationCardStack } = NavigationExperimental; 19 | const { popRoute, jumpTo } = navigationActions; 20 | 21 | class MainNavigation extends Component { 22 | static propTypes = { 23 | navigation: React.PropTypes.shape({ 24 | routes: React.PropTypes.arrayOf(React.PropTypes.object) 25 | }), 26 | tabsIndex: React.PropTypes.number, 27 | dispatch: React.PropTypes.func, 28 | closeWarningModal: React.PropTypes.func, 29 | modal: React.PropTypes.shape({ 30 | LoadingVisible: React.PropTypes.bool, 31 | LoadingMessage: React.PropTypes.string, 32 | WarningVisible: React.PropTypes.bool, 33 | WarningTitle: React.PropTypes.string, 34 | WarningMessage: React.PropTypes.string, 35 | WarningButtons: React.PropTypes.arrayOf( 36 | React.PropTypes.shape() 37 | ) 38 | }), 39 | }; 40 | 41 | componentDidMount() { 42 | BackAndroid.addEventListener('hardwareBackPress', this.handleBackAction); 43 | } 44 | componentWillUnmount() { 45 | BackAndroid.removeEventListener('hardwareBackPress', this.handleBackAction); 46 | } 47 | 48 | handleBackAction = () => { 49 | let event; 50 | const globalLastRoute = this.props.navigation.routes.slice(-1)[0].key; 51 | 52 | if (globalLastRoute === 'dashboard' && this.props.tabsIndex === 0) { 53 | // from dashboard let's exist app 54 | event = false; 55 | } else if (globalLastRoute === 'login' && this.props.tabsIndex === 0) { 56 | // from login let's exist app 57 | event = false; 58 | } else if (globalLastRoute !== 'dashboard') { 59 | // if we have pushed any other views let's pop it 60 | this.props.dispatch(popRoute('global')); 61 | event = true; 62 | } else { 63 | // jump to home if in another view 64 | this.props.dispatch(jumpTo(0, 'ApplicationTabs')); 65 | event = true; 66 | } 67 | 68 | return event; 69 | }; 70 | 71 | renderScene = (props) => { 72 | let scene = null; 73 | 74 | switch (props.scene.route.key) { 75 | case 'login': 76 | scene = ; 77 | break; 78 | case 'register': 79 | scene = ; 80 | break; 81 | case 'loginEmail': 82 | scene = ; 83 | break; 84 | case 'dashboard': 85 | scene = ; 86 | break; 87 | case 'cta': 88 | scene = ; 89 | break; 90 | case 'orderDone': 91 | scene = ; 92 | break; 93 | case 'splash': 94 | default: 95 | scene = (); 96 | } 97 | 98 | return ( 99 | 100 | {scene} 101 | 102 | ); 103 | } 104 | 105 | render() { 106 | return ( 107 | 108 | 113 | 116 | 122 | 123 | ); 124 | } 125 | } 126 | 127 | function mapDispatchToProps(dispatch) { 128 | return { 129 | dispatch, 130 | closeWarningModal: bindActionCreators(closeWarningModal, dispatch) 131 | }; 132 | } 133 | 134 | function mapStateToProps(state) { 135 | return { 136 | navigation: state.globalNavigation, 137 | tabsIndex: state.tabs.index, 138 | modal: state.modals 139 | }; 140 | } 141 | 142 | export default connect(mapStateToProps, mapDispatchToProps)(MainNavigation); 143 | -------------------------------------------------------------------------------- /js/containers/Register/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, Alert, TouchableOpacity, Text, ScrollView } from 'react-native'; 3 | import t from 'tcomb-form-native'; 4 | import { connect } from 'react-redux'; 5 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 6 | import { bindActionCreators } from 'redux'; 7 | import Icon from 'react-native-vector-icons/MaterialIcons'; 8 | 9 | import styles from './styles'; 10 | import Spinner from '../../components/spiner'; 11 | import { validateEmail, validatePassword } from '../../utils/validators'; 12 | import formStyles from '../../styles/form-white'; 13 | import { COLOR_WHITE } from '../../styles/colors'; 14 | import { signUp, signUpSaveForms, cleanErrors } from '../../actions/auth'; 15 | 16 | const { popRoute } = navigationActions; 17 | const Form = t.form.Form; 18 | 19 | const generalFormFields = { 20 | name: t.String, 21 | email: validateEmail, 22 | password: validatePassword, 23 | confirm_password: validatePassword, 24 | }; 25 | 26 | const options = { 27 | stylesheet: formStyles, 28 | fields: { 29 | name: { 30 | error: 'This field is required', 31 | underlineColorAndroid: COLOR_WHITE, 32 | }, 33 | email: { 34 | error: 'This field should be a valid email', 35 | underlineColorAndroid: COLOR_WHITE, 36 | keyboardType: 'email-address' 37 | }, 38 | password: { 39 | password: true, 40 | secureTextEntry: true, 41 | error: 'This field is required', 42 | placeholder: 'Must have at least 6 characters', 43 | placeholderTextColor: COLOR_WHITE 44 | }, 45 | confirm_password: { 46 | password: true, 47 | secureTextEntry: true, 48 | error: 'This field is required', 49 | }, 50 | } 51 | }; 52 | 53 | const registerForm = t.struct(generalFormFields); 54 | 55 | 56 | class Register extends Component { 57 | 58 | static propTypes = { 59 | actionsSignUpSaveForms: React.PropTypes.func, 60 | cleanErrors: React.PropTypes.func, 61 | isFetching: React.PropTypes.bool, 62 | dispatch: React.PropTypes.func, 63 | signUp: React.PropTypes.func, 64 | formData: React.PropTypes.shape(), 65 | errorCode: React.PropTypes.number, 66 | errorObject: React.PropTypes.shape({ 67 | non_field_errors: React.PropTypes.arrayOf(React.PropTypes.string) 68 | }), 69 | }; 70 | 71 | static contextTypes = { 72 | analytics: React.PropTypes.object 73 | }; 74 | 75 | constructor(props) { 76 | super(props); 77 | this.state = { 78 | formOptions: options, 79 | }; 80 | } 81 | 82 | componentDidMount() { 83 | this.context.analytics.trackScreenView('Register'); 84 | } 85 | 86 | componentWillReceiveProps(nextProps) { 87 | const errorState = {}; 88 | Object.keys(generalFormFields).forEach((item) => { 89 | errorState[item] = { 90 | hasError: { $set: false }, 91 | error: { $set: '' } 92 | }; 93 | }); 94 | if (nextProps.auth.errorObject) { 95 | Object.keys(nextProps.auth.errorObject).forEach((item) => { 96 | errorState[item] = { 97 | hasError: { $set: true }, 98 | error: { $set: nextProps.auth.errorObject[item][0] } 99 | }; 100 | }); 101 | } 102 | 103 | const newFormOptions = t.update(this.state.formOptions, { fields: errorState }); 104 | this.setState({ 105 | formOptions: newFormOptions 106 | }); 107 | } 108 | 109 | submit = () => { 110 | this.form.validate(); 111 | const value = this.form.getValue(); 112 | 113 | if (value) { 114 | this.props.actionsSignUpSaveForms(value); 115 | this.props.signUp(value); 116 | } 117 | } 118 | 119 | render() { 120 | let renderScene; 121 | if (this.props.isFetching) { 122 | renderScene = ; 123 | } else { 124 | renderScene = ( 125 | 126 | { this.props.dispatch(popRoute('global')); }} 130 | /> 131 | 132 | 133 | { this.form = c; }} 134 | type={registerForm} 135 | value={this.props.formData} 136 | options={this.state.formOptions} 137 | /> 138 | { this.submit(); }} 139 | style={styles.registerButton} 140 | > 141 | 142 | Register 143 | 144 | 145 | 146 | 147 | 148 | ); 149 | } 150 | 151 | if (this.props.errorCode >= 400 && typeof this.props.errorObject.non_field_errors !== 'undefined') { 152 | Alert.alert('Server Error', this.props.errorObject.non_field_errors[0]); 153 | this.props.cleanErrors(); 154 | } 155 | 156 | return renderScene; 157 | } 158 | } 159 | 160 | function mapDispatchToProps(dispatch) { 161 | return { 162 | dispatch, 163 | actionsSignUpSaveForms: bindActionCreators(signUpSaveForms, dispatch), 164 | signUp: bindActionCreators(signUp, dispatch), 165 | cleanErrors: bindActionCreators(cleanErrors, dispatch) 166 | 167 | }; 168 | } 169 | 170 | function mapStateToProps(state) { 171 | return { 172 | isFetching: state.auth.isAuthenticating, 173 | formData: state.auth.formData, 174 | errorCode: state.auth.errorCode, 175 | errorObject: state.auth.errorObject, 176 | navigation: state.globalNavigation, 177 | auth: state.auth, 178 | }; 179 | } 180 | 181 | export default connect(mapStateToProps, mapDispatchToProps)(Register); 182 | -------------------------------------------------------------------------------- /js/containers/Register/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { PRIMARY_COLOR, COLOR_WHITE } from '../../styles/colors'; 3 | 4 | const styles = StyleSheet.create({ 5 | toolbar: { 6 | height: 56, 7 | backgroundColor: PRIMARY_COLOR 8 | }, 9 | container: { 10 | flex: 1, 11 | backgroundColor: PRIMARY_COLOR, 12 | }, 13 | contentContaier: { 14 | padding: 30, 15 | }, 16 | registerButton: { 17 | backgroundColor: COLOR_WHITE, 18 | padding: 20, 19 | borderRadius: 6, 20 | marginBottom: 20, 21 | alignItems: 'center' 22 | }, 23 | registerButtonTitle: { 24 | fontSize: 18, 25 | color: PRIMARY_COLOR, 26 | } 27 | }); 28 | 29 | export { styles as default }; 30 | -------------------------------------------------------------------------------- /js/containers/Splash/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Text, 4 | View, 5 | Image 6 | } from 'react-native'; 7 | import { actions as navigationActions } from 'react-native-navigation-redux-helpers'; 8 | import { connect } from 'react-redux'; 9 | import Storage from 'react-native-simple-store'; 10 | import { Crashlytics } from 'react-native-fabric'; 11 | import { bindActionCreators } from 'redux'; 12 | import Config from 'react-native-config'; 13 | import SplashStyles from './styles'; 14 | import ICON from '../../images/icon.png'; 15 | import { userLoginSuccess } from '../../actions/auth'; 16 | import { VERSION_PATCH } from '../../utils/config'; 17 | 18 | const { replaceAtIndex } = navigationActions; 19 | 20 | class SplashPage extends Component { 21 | 22 | static propTypes = { 23 | dispatch: React.PropTypes.func, 24 | userLoginSuccess: React.PropTypes.func, 25 | }; 26 | 27 | static contextTypes = { 28 | analytics: React.PropTypes.object 29 | }; 30 | 31 | 32 | componentDidMount() { 33 | Crashlytics.setString('currentScreen', 'Splash'); 34 | Crashlytics.setString('currentVersion', `v${Config.APP_VERSION_NAME}.${VERSION_PATCH}`); 35 | 36 | setTimeout(() => { 37 | Storage.get('user').then((user) => { 38 | if (user) { 39 | this.setState({ loginState: true }); 40 | this.props.userLoginSuccess(user.token, user.user); 41 | 42 | const props = { 43 | firstName: user.first_name, 44 | lastName: user.last_name, 45 | email: user.email, 46 | }; 47 | this.context.analytics.setUserId(user.id, props); 48 | 49 | this.props.dispatch(replaceAtIndex(0, { 50 | key: 'dashboard', 51 | title: 'Dashboard' 52 | }, 'global')); 53 | } else { 54 | this.props.dispatch(replaceAtIndex(0, { 55 | key: 'login', 56 | title: 'Login' 57 | }, 'global')); 58 | } 59 | }); 60 | }, 1000); 61 | } 62 | 63 | 64 | render() { 65 | return ( 66 | 67 | 68 | My Store Foo 69 | 70 | ); 71 | } 72 | } 73 | 74 | const mapDispatchToProps = (dispatch) => { 75 | return { 76 | dispatch, 77 | userLoginSuccess: bindActionCreators(userLoginSuccess, dispatch) 78 | }; 79 | }; 80 | 81 | function mapStateToProps(state) { 82 | return { 83 | }; 84 | } 85 | 86 | export default connect(mapStateToProps, mapDispatchToProps)(SplashPage); 87 | -------------------------------------------------------------------------------- /js/containers/Splash/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { 3 | PRIMARY_COLOR, 4 | COLOR_WHITE 5 | } from '../../styles/colors'; 6 | 7 | export default StyleSheet.create({ 8 | container: { 9 | flex: 1, 10 | backgroundColor: COLOR_WHITE, 11 | alignItems: 'center', 12 | justifyContent: 'center' 13 | }, 14 | image: { 15 | alignItems: 'center', 16 | justifyContent: 'center' 17 | }, 18 | title: { 19 | fontSize: 30, 20 | color: PRIMARY_COLOR, 21 | fontFamily: 'Rubik-Regular', 22 | textAlign: 'center', 23 | margin: 10 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /js/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seedstars/reactnative-mobile-app-base/d2476e6932cfaee118461fe74f8c863c4d3c9dc9/js/images/icon.png -------------------------------------------------------------------------------- /js/images/no_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seedstars/reactnative-mobile-app-base/d2476e6932cfaee118461fe74f8c863c4d3c9dc9/js/images/no_avatar.png -------------------------------------------------------------------------------- /js/middlewares/Crashlytics.js: -------------------------------------------------------------------------------- 1 | import { Crashlytics } from 'react-native-fabric'; 2 | import sizeOf from 'js-sizeof'; 3 | 4 | const actions = []; 5 | 6 | export default function crashlyticsLogger() { 7 | return (next) => { 8 | return (action) => { 9 | actions.push(action); 10 | if (actions.length > 20) { 11 | actions.shift(); 12 | } 13 | 14 | actions.forEach((act, idx) => { 15 | let actionObject = JSON.stringify(act); 16 | if (act.type && sizeOf(actionObject) > 950) { // Crashlytics setString only allows for 1kb keypairs 17 | actionObject = JSON.stringify({ type: act.type, payload: 'Too big for Crashlytics' }); 18 | } 19 | 20 | Crashlytics.setString(`action${actions.length - idx}`, actionObject); 21 | }); 22 | 23 | return next(action); 24 | }; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /js/middlewares/index.js: -------------------------------------------------------------------------------- 1 | import crashlyticsLogger from './Crashlytics'; 2 | 3 | export default { 4 | crashlyticsLogger 5 | }; 6 | -------------------------------------------------------------------------------- /js/reducers/applicationTabs.js: -------------------------------------------------------------------------------- 1 | import { tabReducer } from 'react-native-navigation-redux-helpers'; 2 | 3 | const tabs = { 4 | routes: [ 5 | { key: 'home', title: 'Home', icon: 'home' }, 6 | { key: 'activity2', title: 'Activity 2', icon: 'looks-two' } 7 | ], 8 | text: [ 9 | ], 10 | key: 'ApplicationTabs', 11 | index: 0 12 | }; 13 | 14 | module.exports = tabReducer(tabs); 15 | -------------------------------------------------------------------------------- /js/reducers/auth.js: -------------------------------------------------------------------------------- 1 | import { 2 | AUTH_LOGIN_USER_REQUEST, 3 | AUTH_LOGIN_USER_SUCCESS, 4 | AUTH_LOGIN_USER_FAILURE, 5 | AUTH_LOGIN_USER_EXPIRED, 6 | AUTH_LOGIN_CLEAN_ERRORS, 7 | AUTH_LOGOUT_USER, 8 | AUTH_SAVE_USERS_INFORMATION, 9 | AUTH_SIGNUP_REQUEST, 10 | AUTH_SIGNUP_SUCCESS, 11 | SUBMIT_SIGNUP_FORM_SAVE, 12 | SUBMIT_LOGIN_FORM_SAVE 13 | } from '../constants'; 14 | import { createReducer } from '../utils'; 15 | 16 | const initialState = { 17 | token: null, 18 | firstName: null, 19 | lastName: null, 20 | isAuthenticated: false, 21 | isAuthenticating: false, 22 | authenticationExpired: false, 23 | errorText: null, 24 | facebook_uid: null, 25 | outdated: false, 26 | isFetching: false, 27 | errorCode: null, 28 | errorObject: null, 29 | formData: {}, 30 | }; 31 | 32 | export default createReducer(initialState, { 33 | [AUTH_LOGIN_USER_REQUEST]: (state, payload) => { 34 | return Object.assign({}, state, { 35 | authenticationExpired: false, 36 | isAuthenticating: true, 37 | errorCode: null, 38 | errorObject: null, 39 | isFetching: true 40 | }); 41 | }, 42 | [AUTH_LOGIN_USER_SUCCESS]: (state, payload) => { 43 | return Object.assign({}, state, { 44 | isAuthenticating: false, 45 | isAuthenticated: true, 46 | authenticationExpired: false, 47 | token: payload.token, 48 | outdated: payload.outdated, 49 | firstName: payload.user.first_name, 50 | lastName: payload.user.last_name, 51 | facebook_uid: payload.user.facebook_uid, 52 | email: payload.user.email, 53 | errorCode: null, 54 | errorObject: null, 55 | isFetching: false 56 | }); 57 | }, 58 | [AUTH_LOGIN_USER_FAILURE]: (state, payload) => { 59 | return Object.assign({}, state, { 60 | isAuthenticating: false, 61 | isAuthenticated: false, 62 | authenticationExpired: false, 63 | token: null, 64 | firstName: null, 65 | lastName: null, 66 | facebook_uid: null, 67 | errorCode: payload.error, 68 | errorObject: payload.errorObject, 69 | isFetching: false 70 | 71 | }); 72 | }, 73 | [AUTH_LOGIN_USER_EXPIRED]: (state, payload) => { 74 | return Object.assign({}, state, { 75 | isAuthenticating: false, 76 | isAuthenticated: false, 77 | authenticationExpired: true, 78 | token: null, 79 | firstName: null, 80 | lastName: null, 81 | facebook_uid: null, 82 | isFetching: false, 83 | errorCode: payload.error, 84 | errorObject: payload.errorObject 85 | }); 86 | }, 87 | [AUTH_LOGIN_CLEAN_ERRORS]: (state, payload) => { 88 | return Object.assign({}, state, { 89 | isAuthenticating: false, 90 | isAuthenticated: false, 91 | authenticationExpired: false, 92 | token: null, 93 | firstName: null, 94 | lastName: null, 95 | facebook_uid: null, 96 | errorCode: null, 97 | errorObject: null 98 | }); 99 | }, 100 | [AUTH_SIGNUP_REQUEST]: (state, payload) => { 101 | return Object.assign({}, state, { 102 | isAuthenticating: true, 103 | error: null, 104 | authenticationExpired: false, 105 | isFetching: true 106 | }); 107 | }, 108 | [AUTH_SIGNUP_SUCCESS]: (state, payload) => { 109 | return Object.assign({}, state, { 110 | isAuthenticating: false, 111 | isAuthenticated: true, 112 | authenticationExpired: false, 113 | token: payload.token, 114 | outdated: payload.outdated, 115 | email: payload.user.email, 116 | error: null, 117 | signup: true, 118 | fillInformation: payload.fillInformation, 119 | isFetching: false 120 | }); 121 | }, 122 | [AUTH_LOGOUT_USER]: (state, payload) => { 123 | return Object.assign({}, state, { 124 | isAuthenticated: false, 125 | authenticationExpired: false, 126 | token: null, 127 | firstName: null, 128 | lastName: null, 129 | errorCode: null, 130 | errorObject: null 131 | }); 132 | }, 133 | [AUTH_SAVE_USERS_INFORMATION]: (state, payload) => { 134 | return Object.assign({}, state, { 135 | token: payload.user.token, 136 | firstName: payload.user.user.first_name, 137 | lastName: payload.user.user.last_name, 138 | phoneNumber: payload.user.user.phone_number, 139 | facebook_uid: payload.user.user.facebook_uid, 140 | email: payload.user.user.email, 141 | errorCode: null, 142 | errorObject: null 143 | }); 144 | }, 145 | [SUBMIT_SIGNUP_FORM_SAVE]: (state, payload) => { 146 | return Object.assign({}, state, { 147 | formData: payload.data 148 | }); 149 | }, 150 | [SUBMIT_LOGIN_FORM_SAVE]: (state, payload) => { 151 | return Object.assign({}, state, { 152 | formData: payload.data 153 | }); 154 | }, 155 | 156 | 157 | }); 158 | -------------------------------------------------------------------------------- /js/reducers/forms.js: -------------------------------------------------------------------------------- 1 | import { 2 | SUBMIT_ORDER_DEVICE_FORM_REQUEST, 3 | SUBMIT_ORDER_DEVICE_FORM_SAVE, 4 | SUBMIT_ORDER_DEVICE_FORM_SUCCESS, 5 | SUBMIT_ORDER_DEVICE_FORM_FAILURE 6 | } from '../constants'; 7 | import { createReducer } from '../utils'; 8 | 9 | const initialState = { 10 | isFetching: false, 11 | errorCode: null, 12 | errorObject: null, 13 | formData: {}, 14 | isChargingCard: false, 15 | errorText: null, 16 | tab: 0, 17 | }; 18 | 19 | export default createReducer(initialState, { 20 | [SUBMIT_ORDER_DEVICE_FORM_REQUEST]: (state, payload) => { 21 | return Object.assign({}, state, { 22 | isFetching: true, 23 | formData: payload.data 24 | }); 25 | }, 26 | [SUBMIT_ORDER_DEVICE_FORM_SAVE]: (state, payload) => { 27 | return Object.assign({}, state, { 28 | formData: payload.data 29 | }); 30 | }, 31 | [SUBMIT_ORDER_DEVICE_FORM_SUCCESS]: (state, payload) => { 32 | return Object.assign({}, state, { 33 | isFetching: false, 34 | formData: {}, 35 | errorCode: null, 36 | errorObject: null 37 | }); 38 | }, 39 | [SUBMIT_ORDER_DEVICE_FORM_FAILURE]: (state, payload) => { 40 | return Object.assign({}, state, { 41 | isFetching: false, 42 | errorCode: payload.errorCode, 43 | errorObject: payload.errorObject 44 | }); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /js/reducers/globalNavigator.js: -------------------------------------------------------------------------------- 1 | import { cardStackReducer } from 'react-native-navigation-redux-helpers'; 2 | 3 | const initialState = { 4 | key: 'global', 5 | index: 0, 6 | routes: [ 7 | { 8 | key: 'splash', 9 | index: 0 10 | } 11 | ], 12 | }; 13 | 14 | module.exports = cardStackReducer(initialState); 15 | -------------------------------------------------------------------------------- /js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import globalNavigationReducer from './globalNavigator'; 3 | import tabs from './applicationTabs'; 4 | import authReducer from './auth'; 5 | import modals from './modals'; 6 | import form from './forms'; 7 | 8 | const appReducers = combineReducers({ 9 | globalNavigation: globalNavigationReducer, 10 | tabs, 11 | modals, 12 | form, 13 | auth: authReducer, 14 | }); 15 | export default appReducers; 16 | -------------------------------------------------------------------------------- /js/reducers/modals.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOADING_MODAL_OPEN, 3 | LOADING_MODAL_CLOSE, 4 | WARNING_MODAL_OPEN, 5 | WARNING_MODAL_CLOSE, 6 | BUYDEVICE_MODAL_OPEN, 7 | BUYDEVICE_MODAL_CLOSE 8 | } from '../constants'; 9 | import { createReducer } from '../utils'; 10 | 11 | const initialState = { 12 | BuyDevicevisible: false, 13 | LoadingVisible: false, 14 | LoadingMessage: null, 15 | WarningVisible: false, 16 | WarningTitle: null, 17 | WarningMessage: null, 18 | WarningButtons: [] 19 | 20 | }; 21 | 22 | export default createReducer(initialState, { 23 | [BUYDEVICE_MODAL_OPEN]: (state, payload) => { 24 | return Object.assign({}, state, { 25 | BuyDevicevisible: true 26 | }); 27 | }, 28 | [BUYDEVICE_MODAL_CLOSE]: (state, payload) => { 29 | return Object.assign({}, state, initialState); 30 | }, 31 | [LOADING_MODAL_OPEN]: (state, payload) => { 32 | return Object.assign({}, state, { 33 | LoadingVisible: true, 34 | LoadingMessage: payload.message 35 | }); 36 | }, 37 | [LOADING_MODAL_CLOSE]: (state, payload) => { 38 | return Object.assign({}, state, initialState); 39 | }, 40 | [WARNING_MODAL_OPEN]: (state, payload) => { 41 | return Object.assign({}, state, { 42 | WarningVisible: true, 43 | WarningTitle: payload.title, 44 | WarningMessage: payload.message, 45 | WarningButtons: payload.buttons || [] 46 | }); 47 | }, 48 | [WARNING_MODAL_CLOSE]: (state, payload) => { 49 | return Object.assign({}, state, initialState); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /js/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import createLogger from 'redux-logger'; 4 | import reducers from '../reducers'; 5 | import middlewares from '../middlewares'; 6 | 7 | export default function configureStore() { 8 | const logger = createLogger(); 9 | const middleware = applyMiddleware(thunk, logger, middlewares.crashlyticsLogger); 10 | const createStoreWithMiddleware = compose( 11 | middleware, 12 | ); 13 | return createStoreWithMiddleware(createStore)(reducers); 14 | } 15 | -------------------------------------------------------------------------------- /js/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | const AppStyles = StyleSheet.create({ 4 | container: { 5 | flex: 1 6 | } 7 | }); 8 | 9 | export { AppStyles as default }; 10 | -------------------------------------------------------------------------------- /js/styles/base.js: -------------------------------------------------------------------------------- 1 | import { 2 | Dimensions 3 | } from 'react-native'; 4 | 5 | export const SCREEN_WIDTH = Dimensions.get('window').width; 6 | export const SCREEN_HEIGHT = Dimensions.get('window').height; 7 | 8 | // Full Screen Container 9 | export const CONTAINER_DISTANCE_TOP = 47; 10 | export const CONTAINER_DISTANCE_SIDE = 32; 11 | export const CONTAINER_WIDTH = SCREEN_WIDTH - (CONTAINER_DISTANCE_SIDE * 30); 12 | -------------------------------------------------------------------------------- /js/styles/colors.js: -------------------------------------------------------------------------------- 1 | export const PRIMARY_COLOR = '#4F81BD'; 2 | export const SECONDARY_COLOR = '#525B5F'; 3 | export const DARKER_PRIMARY = '#304E73'; 4 | 5 | export const COLOR_WHITE = '#FFFFFF'; 6 | export const COLOR_SILVER_DARK = '#959595'; 7 | export const COLOR_SILVER_LIGHT = '#E8E8E8'; 8 | 9 | export const COLOR_LIGHT_GREY = '#FAFAFA'; 10 | export const COLOR_DARK_GREY = '#f5f5f5'; 11 | 12 | 13 | export const COLOR_ERROR = '#E74C3C'; 14 | 15 | export const COLOR_BLACK = '#000000'; 16 | export const COLOR_LIGHT_GRAY = '#cccccc'; 17 | export const COLOR_GRAY = '#777777'; 18 | export const COLOR_BACKDROP = 'rgba(0, 0, 0, 0.6)'; 19 | -------------------------------------------------------------------------------- /js/styles/form-white.js: -------------------------------------------------------------------------------- 1 | import { 2 | COLOR_GRAY, 3 | COLOR_LIGHT_GRAY, 4 | PRIMARY_COLOR, 5 | COLOR_LIGHT_GREY, 6 | COLOR_ERROR, 7 | COLOR_WHITE 8 | } from './colors'; 9 | import { SCREEN_WIDTH } from './base'; 10 | 11 | const FONT_SIZE = 15; 12 | const FONT_WEIGHT = '500'; 13 | 14 | export default Object.freeze({ 15 | fieldset: {}, 16 | formGroup: { 17 | normal: { 18 | marginBottom: 10 19 | }, 20 | error: { 21 | marginBottom: 10 22 | } 23 | }, 24 | controlLabel: { 25 | normal: { 26 | color: COLOR_WHITE, 27 | fontSize: FONT_SIZE, 28 | marginBottom: 7, 29 | fontWeight: FONT_WEIGHT 30 | }, 31 | error: { 32 | color: COLOR_ERROR, 33 | fontSize: FONT_SIZE, 34 | marginBottom: 7, 35 | fontWeight: FONT_WEIGHT 36 | } 37 | }, 38 | helpBlock: { 39 | normal: { 40 | color: COLOR_WHITE, 41 | fontSize: 10, 42 | marginBottom: 2 43 | }, 44 | error: { 45 | color: PRIMARY_COLOR, 46 | fontSize: 10, 47 | marginBottom: 2 48 | } 49 | }, 50 | errorBlock: { 51 | fontSize: 14, 52 | marginBottom: 2, 53 | color: COLOR_ERROR 54 | }, 55 | textbox: { 56 | normal: { 57 | color: COLOR_WHITE, 58 | fontSize: FONT_SIZE, 59 | height: 36, 60 | padding: 7, 61 | borderRadius: 4, 62 | borderColor: COLOR_LIGHT_GRAY, 63 | borderWidth: 0, 64 | marginBottom: 5 65 | }, 66 | error: { 67 | color: COLOR_WHITE, 68 | fontSize: FONT_SIZE, 69 | height: 36, 70 | padding: 7, 71 | borderRadius: 4, 72 | borderColor: COLOR_WHITE, 73 | borderWidth: 0, 74 | marginBottom: 5 75 | }, 76 | notEditable: { 77 | fontSize: FONT_SIZE, 78 | height: 36, 79 | padding: 7, 80 | borderRadius: 4, 81 | borderColor: COLOR_LIGHT_GRAY, 82 | borderWidth: 0, 83 | marginBottom: 5, 84 | color: COLOR_GRAY, 85 | backgroundColor: COLOR_LIGHT_GREY 86 | } 87 | }, 88 | checkbox: { 89 | normal: { 90 | marginBottom: 4 91 | }, 92 | error: { 93 | marginBottom: 4 94 | } 95 | }, 96 | select: { 97 | normal: { 98 | width: SCREEN_WIDTH * 0.8, 99 | marginBottom: 4 100 | }, 101 | error: { 102 | width: SCREEN_WIDTH * 0.8, 103 | marginBottom: 4 104 | } 105 | }, 106 | datepicker: { 107 | normal: { 108 | marginBottom: 4 109 | }, 110 | error: { 111 | marginBottom: 4 112 | } 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /js/styles/form.js: -------------------------------------------------------------------------------- 1 | import { 2 | COLOR_GRAY, 3 | COLOR_LIGHT_GRAY, 4 | COLOR_BLACK, 5 | PRIMARY_COLOR, 6 | COLOR_LIGHT_GREY, 7 | SECONDARY_COLOR, 8 | COLOR_ERROR 9 | } from './colors'; 10 | import { SCREEN_WIDTH } from './base'; 11 | 12 | const FONT_SIZE = 15; 13 | const FONT_WEIGHT = '500'; 14 | 15 | export default Object.freeze({ 16 | fieldset: {}, 17 | formGroup: { 18 | normal: { 19 | marginBottom: 10 20 | }, 21 | error: { 22 | marginBottom: 10 23 | } 24 | }, 25 | controlLabel: { 26 | normal: { 27 | color: SECONDARY_COLOR, 28 | fontSize: FONT_SIZE, 29 | marginBottom: 7, 30 | fontWeight: FONT_WEIGHT 31 | }, 32 | error: { 33 | color: COLOR_ERROR, 34 | fontSize: FONT_SIZE, 35 | marginBottom: 7, 36 | fontWeight: FONT_WEIGHT 37 | } 38 | }, 39 | helpBlock: { 40 | normal: { 41 | color: PRIMARY_COLOR, 42 | fontSize: 10, 43 | marginBottom: 2 44 | }, 45 | error: { 46 | color: PRIMARY_COLOR, 47 | fontSize: 10, 48 | marginBottom: 2 49 | } 50 | }, 51 | errorBlock: { 52 | fontSize: 14, 53 | marginBottom: 2, 54 | color: COLOR_ERROR 55 | }, 56 | textbox: { 57 | normal: { 58 | color: COLOR_BLACK, 59 | fontSize: FONT_SIZE, 60 | height: 36, 61 | padding: 7, 62 | borderRadius: 4, 63 | borderColor: COLOR_LIGHT_GRAY, 64 | borderWidth: 0, 65 | marginBottom: 5 66 | }, 67 | error: { 68 | color: COLOR_BLACK, 69 | fontSize: FONT_SIZE, 70 | height: 36, 71 | padding: 7, 72 | borderRadius: 4, 73 | borderColor: PRIMARY_COLOR, 74 | borderWidth: 0, 75 | marginBottom: 5 76 | }, 77 | notEditable: { 78 | fontSize: FONT_SIZE, 79 | height: 36, 80 | padding: 7, 81 | borderRadius: 4, 82 | borderColor: COLOR_LIGHT_GRAY, 83 | borderWidth: 0, 84 | marginBottom: 5, 85 | color: COLOR_GRAY, 86 | backgroundColor: COLOR_LIGHT_GREY 87 | } 88 | }, 89 | checkbox: { 90 | normal: { 91 | marginBottom: 4 92 | }, 93 | error: { 94 | marginBottom: 4 95 | } 96 | }, 97 | select: { 98 | normal: { 99 | width: SCREEN_WIDTH * 0.8, 100 | marginBottom: 4 101 | }, 102 | error: { 103 | width: SCREEN_WIDTH * 0.8, 104 | marginBottom: 4 105 | } 106 | }, 107 | datepicker: { 108 | normal: { 109 | marginBottom: 4 110 | }, 111 | error: { 112 | marginBottom: 4 113 | } 114 | } 115 | }); 116 | -------------------------------------------------------------------------------- /js/utils/AnalyticsUtils.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Children } from 'react'; 2 | import { GoogleAnalyticsTracker } from 'react-native-google-analytics-bridge'; 3 | import { Crashlytics } from 'react-native-fabric'; 4 | import { AppEventsLogger } from 'react-native-fbsdk'; 5 | import Config from 'react-native-config'; 6 | 7 | 8 | export default class AnalyticsProvider extends Component { 9 | static propTypes = { 10 | children: React.PropTypes.shape() 11 | }; 12 | 13 | static childContextTypes = { 14 | analytics: React.PropTypes.object 15 | }; 16 | 17 | constructor(props, context) { 18 | super(props, context); 19 | 20 | this.googleAnalytics = new GoogleAnalyticsTracker(Config.ANALYTICS_KEY); 21 | this.googleAnalytics.allowIDFA(true); 22 | 23 | this.facebook = AppEventsLogger; 24 | 25 | this.crashlytics = Crashlytics; 26 | } 27 | 28 | getChildContext() { 29 | return { 30 | analytics: { 31 | logEvent: (event) => { 32 | this.facebook.logEvent(event); 33 | }, 34 | 35 | trackScreenView: (screen) => { 36 | this.crashlytics.setString('currentScreen', screen); 37 | this.googleAnalytics.trackScreenView(screen); 38 | this.facebook.logEvent(`Show screen '${screen}'`); 39 | }, 40 | 41 | setUserId: (userId, props) => { 42 | this.crashlytics.setUserName(`${props.firstName} ${props.lastName}`); 43 | this.crashlytics.setUserEmail(props.email); 44 | this.crashlytics.setUserIdentifier(userId); 45 | } 46 | } 47 | }; 48 | } 49 | 50 | render() { 51 | return Children.only(this.props.children); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /js/utils/config.js: -------------------------------------------------------------------------------- 1 | import Config from 'react-native-config'; 2 | 3 | export const SERVER_URL = Config.BACKEND_URL; 4 | // this is patch version that get's merged with APP_VERSION_NAME to create full version ie: 1.0.0 5 | export const VERSION_PATCH = '0'; 6 | 7 | -------------------------------------------------------------------------------- /js/utils/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | NetInfo, 3 | } from 'react-native'; 4 | 5 | export function createReducer(initialState, reducerMap) { 6 | return (state = initialState, action) => { 7 | const reducer = reducerMap[action.type]; 8 | return reducer ? reducer(state, action.payload) : state; 9 | }; 10 | } 11 | 12 | export function checkHttpStatus(response) { 13 | if (response.status >= 200 && response.status < 300) { 14 | return response; 15 | } 16 | 17 | const error = new Error(response.statusText); 18 | error.response = response; 19 | throw error; 20 | } 21 | 22 | export function parseJSON(response) { 23 | return response.json(); 24 | } 25 | 26 | export function checkInternet() { 27 | return new Promise((resolve, reject) => { 28 | NetInfo.fetch().then((netInfo) => { 29 | const info = netInfo.toLowerCase(); 30 | resolve((info !== 'none' || info !== 'unknown')); 31 | }).catch((err) => { 32 | reject(err); 33 | }); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /js/utils/transformers.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export const phoneNumberTransformer = { 4 | format: (value) => { 5 | return value; 6 | }, 7 | parse: (phoneNumber) => { 8 | const phone = typeof phoneNumber !== 'undefined' ? phoneNumber : ''; 9 | const internationalPhoneNumber = phone.startsWith('00') ? phone.replace(/^00/, '+') : phone; 10 | return internationalPhoneNumber.replace(/\s/g, ''); 11 | } 12 | }; 13 | 14 | 15 | export const dateFieldTransformer = { 16 | format: (value) => { 17 | return value ? moment(value).format('YYYY-MM-DD') : moment().format('YYYY-MM-DD'); 18 | }, 19 | parse: (str) => { 20 | return str ? new Date(str) : null; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /js/utils/validators.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const t = require('tcomb-form-native'); 4 | 5 | export const validateEmail = t.refinement(t.String, (email) => { 6 | return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(email.replace(/\s/g, '')); // eslint-disable-line 7 | }); 8 | 9 | export const PhoneNumber = t.refinement(t.String, (phoneNumber) => { 10 | return /^\+[1-9]\d{7,15}$/.test(phoneNumber.replace(/\s/g, '')); 11 | }); 12 | 13 | export const validateDate = t.refinement(t.Date, (date) => { 14 | const todayDate = new Date(); 15 | const dayOfToday = moment(date).day(); 16 | return date >= todayDate && dayOfToday !== 0; 17 | }); 18 | 19 | export const validatePassword = t.refinement(t.String, (password) => { 20 | return /^([a-zA-Z0-9_-]){6,}$/.test(password.replace(/\s/g, '')); 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seedstarsbase", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "lintjs": "eslint -c .eslintrc js || true" 8 | }, 9 | "dependencies": { 10 | "buffer": "5.0.1", 11 | "js-sizeof": "0.0.1", 12 | "moment": "2.17.0", 13 | "react": "15.3.2", 14 | "react-addons-shallow-compare": "15.3.2", 15 | "react-native": "0.37.0", 16 | "react-native-branch": "0.9.0", 17 | "react-native-button": "1.7.1", 18 | "react-native-code-push": "1.16.0-beta", 19 | "react-native-communications": "2.1.3", 20 | "react-native-config": "0.1.2", 21 | "react-native-fabric": "0.3.2", 22 | "react-native-fbsdk": "0.4.0", 23 | "react-native-google-analytics-bridge": "4.0.2", 24 | "react-native-navigation-redux-helpers": "0.5.0", 25 | "react-native-onesignal": "1.2.3", 26 | "react-native-simple-store": "1.1.0", 27 | "react-native-tab-view": "0.0.40", 28 | "react-native-vector-icons": "3.0.0", 29 | "react-redux": "4.4.6", 30 | "redux": "3.6.0", 31 | "redux-logger": "2.7.4", 32 | "redux-thunk": "2.1.0", 33 | "tcomb-form": "0.9.10", 34 | "tcomb-form-native": "0.6.1" 35 | }, 36 | "devDependencies": { 37 | "babel-eslint": "7.1.1", 38 | "eslint": "3.11.1", 39 | "eslint-config-airbnb": "13.0.0", 40 | "eslint-plugin-import": "2.2.0", 41 | "eslint-plugin-jsx-a11y": "2.2.3", 42 | "eslint-plugin-react": "6.7.1", 43 | "eslint-plugin-react-native": "2.2.0", 44 | "remote-redux-devtools": "0.5.4", 45 | "sass-lint": "1.10.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /shippable.yml: -------------------------------------------------------------------------------- 1 | # Build Environment 2 | build_environment: Ubuntu 14.04 3 | 4 | # variables used for slack comunication 5 | env: 6 | global: 7 | - PROJECT="seedstars-base-mobile" 8 | 9 | language: node_js 10 | 11 | python: 12 | - 3.4 13 | 14 | # only build when certain Git branches are updated 15 | branches: 16 | only: 17 | - master 18 | - staging 19 | 20 | install: 21 | - wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.31.6/install.sh | bash 22 | - bash 23 | - nvm install v4.0.0 24 | - npm install 25 | 26 | # configuration to run tests 27 | before_script: 28 | - mkdir -p shippable/testresults 29 | - mkdir -p shippable/codecoverage 30 | - git submodule init 31 | - git submodule update 32 | - ./scripts/get_static_validation.sh 33 | 34 | 35 | # run test scripts 36 | script: 37 | - ./scripts/test_shippable_mobileapp.sh 38 | 39 | # Notification 40 | notifications: 41 | email: 42 | recipients: 43 | - ci@seedstarslabs.com 44 | on_success: change 45 | on_failure: change --------------------------------------------------------------------------------