UPS1 - Signup with Realm
@ -1,7 +1,8 @@
|
||||
{
|
||||
module.exports = {
|
||||
"extends": "rallycoding",
|
||||
"rules": {
|
||||
"max-len": 0
|
||||
"max-len": 0,
|
||||
"react/require-extension": "off"
|
||||
},
|
||||
"globals": {
|
||||
"fetch": true
|
||||
3
.gitignore
vendored
@ -59,3 +59,6 @@ buck-out/
|
||||
|
||||
#Storybook
|
||||
storybook
|
||||
test.js
|
||||
ios/Tagfer.xcodeproj/project.pbxproj
|
||||
package-lock.json
|
||||
|
||||
@ -137,14 +137,15 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-app-settings')
|
||||
compile project(':react-native-image-picker')
|
||||
compile project(':react-native-camera')
|
||||
compile project(':react-native-contacts')
|
||||
compile project(':react-native-sms')
|
||||
compile project(':realm')
|
||||
compile project(':react-native-webview')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-image-picker')
|
||||
compile project(':react-native-gesture-handler')
|
||||
compile project(':realm')
|
||||
compile project(':react-native-contacts')
|
||||
compile project(':react-native-camera')
|
||||
compile project(':react-native-app-settings')
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
|
||||
@ -3,14 +3,15 @@ package com.tagfer;
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.krazylabs.OpenAppSettingsPackage;
|
||||
import com.imagepicker.ImagePickerPackage;
|
||||
import org.reactnative.camera.RNCameraPackage;
|
||||
import com.rt2zz.reactnativecontacts.ReactNativeContacts;
|
||||
import com.tkporter.sendsms.SendSMSPackage;
|
||||
import io.realm.react.RealmReactPackage;
|
||||
import com.reactnativecommunity.webview.RNCWebViewPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.imagepicker.ImagePickerPackage;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
|
||||
import io.realm.react.RealmReactPackage;
|
||||
import com.rt2zz.reactnativecontacts.ReactNativeContacts;
|
||||
import org.reactnative.camera.RNCameraPackage;
|
||||
import com.krazylabs.OpenAppSettingsPackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
@ -31,14 +32,15 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new OpenAppSettingsPackage(),
|
||||
new ImagePickerPackage(),
|
||||
new RNCameraPackage(),
|
||||
new ReactNativeContacts(),
|
||||
SendSMSPackage.getInstance(),
|
||||
new RealmReactPackage(),
|
||||
new RNCWebViewPackage(),
|
||||
new VectorIconsPackage(),
|
||||
new ImagePickerPackage(),
|
||||
new RNGestureHandlerPackage(),
|
||||
new RealmReactPackage()
|
||||
new ReactNativeContacts(),
|
||||
new RNCameraPackage(),
|
||||
new OpenAppSettingsPackage()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
rootProject.name = 'Tagfer'
|
||||
include ':react-native-app-settings'
|
||||
project(':react-native-app-settings').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-app-settings/android')
|
||||
include ':react-native-image-picker'
|
||||
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
|
||||
include ':react-native-camera'
|
||||
project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android')
|
||||
include ':react-native-contacts'
|
||||
project(':react-native-contacts').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-contacts/android')
|
||||
include ':react-native-sms'
|
||||
project(':react-native-sms').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sms/android')
|
||||
include ':realm'
|
||||
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
|
||||
include ':react-native-webview'
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
include ':react-native-image-picker'
|
||||
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
|
||||
include ':react-native-gesture-handler'
|
||||
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
|
||||
include ':realm'
|
||||
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
|
||||
include ':react-native-contacts'
|
||||
project(':react-native-contacts').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-contacts/android')
|
||||
include ':react-native-camera'
|
||||
project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android')
|
||||
include ':react-native-app-settings'
|
||||
project(':react-native-app-settings').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-app-settings/android')
|
||||
|
||||
include ':app'
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@ -22,6 +22,19 @@
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
@ -36,19 +49,5 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
||||
@ -1,34 +1,58 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Spotlight-40.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Spotlight-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Spotlight-40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Logo2.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
BIN
ios/Tagfer/Images.xcassets/AppIcon.appiconset/Icon-60.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
ios/Tagfer/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
ios/Tagfer/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
ios/Tagfer/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
ios/Tagfer/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
BIN
ios/Tagfer/Images.xcassets/AppIcon.appiconset/Logo2.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@ -21,25 +21,9 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>2</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
@ -53,12 +37,26 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>Listen to tunes</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>Send photos using bluetooth</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>Need to add events to your calendar</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Upload photos and scan QR codes</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Allow the user to search for his phone contacts in network</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<string>Find your location</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Record audio clips</string>
|
||||
<key>NSMotionUsageDescription</key>
|
||||
<string>Orientation of device</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Add images to profile</string>
|
||||
<key>NSSpeechRecognitionUsageDescription</key>
|
||||
<string>Speech to text</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Entypo.ttf</string>
|
||||
|
||||
18525
package-lock.json
generated
12
package.json
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "tagfer",
|
||||
"name": "Tagfer",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@ -14,23 +14,25 @@
|
||||
"react-native-app-settings": "^2.0.1",
|
||||
"react-native-camera": "^1.8.0",
|
||||
"react-native-contacts": "^2.2.5",
|
||||
"react-native-country-picker-modal": "^0.6.2",
|
||||
"react-native-country-picker-modal": "^0.7.1",
|
||||
"react-native-elements": "^0.19.1",
|
||||
"react-native-flash-message": "^0.1.10",
|
||||
"react-native-gesture-handler": "^1.0.12",
|
||||
"react-native-image-picker": "^0.28.0",
|
||||
"react-native-masked-text": "^1.9.2",
|
||||
"react-native-permissions": "^1.1.1",
|
||||
"react-native-sms": "^1.8.0",
|
||||
"react-native-vector-icons": "^4.6.0",
|
||||
"react-native-webview": "^2.14.3",
|
||||
"react-navigation": "^3.0.9",
|
||||
"react-native-image-picker": "^0.28.0",
|
||||
"react-native-permissions": "^1.1.1",
|
||||
"realm": "^2.21.1"
|
||||
"realm": "^2.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/react-native": "^4.1.6",
|
||||
"babel-jest": "23.6.0",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-rallycoding": "^3.2.0",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"jest": "23.6.0",
|
||||
"metro-react-native-babel-preset": "0.51.1",
|
||||
"react-test-renderer": "16.6.3"
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
export const GOING = strings('event.attendeeStatus_going');
|
||||
export const INTERESTED = strings('event.attendeeStatus_interested');
|
||||
export const INVITED = strings('event.attendeeStatus_invited');
|
||||
export const NOT_GOING = strings('event.attendeeStatus_notGoing');
|
||||
@ -1,892 +0,0 @@
|
||||
/** Contains the base fields for a contact reuse **/
|
||||
import React, { Component } from 'react';
|
||||
import { Alert, Dimensions, Linking, View } from 'react-native';
|
||||
import { Button, Tile } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { ImagePicker, Permissions } from 'expo';
|
||||
import { contactUpdate, profileUpdate } from '../actions';
|
||||
import { PROFILE_FORM, CONTACT_FORM, SOCIAL_FORM } from './FormTypes';
|
||||
import { Spacer } from './common';
|
||||
import privateKeys from '../../private_keys.json';
|
||||
import colors from '../config/colors.json';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
const width = Dimensions.get('window').width * 0.9;
|
||||
const height = width * 0.57; // ( 2 / 3.5 = 0.57) Business card dimensions
|
||||
const regexObj = {};
|
||||
const visionObj = {};
|
||||
// Since we have duplicate keys between contact and profile we can't use this.props
|
||||
// so we track what has been populated here
|
||||
const propsAlreadySet = {};
|
||||
|
||||
class BizCard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
cameraRollStatus: '',
|
||||
text: null,
|
||||
company: null,
|
||||
jobTitle: null,
|
||||
city: null,
|
||||
region: null
|
||||
};
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
if (this.props.isScanBizCardScreen) {
|
||||
this.onTakePhotoButtonPress();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends user to the gallery to upload image from device
|
||||
**/
|
||||
onPickImageButtonPress = async () => {
|
||||
if (this.state.cameraRollStatus !== 'granted') {
|
||||
await Permissions.askAsync(Permissions.CAMERA_ROLL)
|
||||
.then((data) => {
|
||||
if (data.status !== 'granted') {
|
||||
Alert.alert(strings('common.error.permissionDenied_cameraRoll'));
|
||||
Linking.openURL('app-settings:');
|
||||
return;
|
||||
}
|
||||
}).catch(err => console.error('askAsync camera roll error ', err));
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [3.5, 2],
|
||||
base64: true,
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the camera for the user to take a new photo
|
||||
**/
|
||||
onTakePhotoButtonPress = async () => {
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
base64: true
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the current permission state of the app for camera roll/photos
|
||||
**/
|
||||
setCameraRollStatus = async () => {
|
||||
const { status } = await Permissions.getAsync(Permissions.CAMERA_ROLL);
|
||||
this.setState({ cameraRollStatus: status });
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the chosen image in base64 and saves the base64 and submits it for text recognition
|
||||
**/
|
||||
processImage = async (response) => {
|
||||
this.setState({ text: strings('bizCard.loading') });
|
||||
propsAlreadySet.base64 = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'contactBizCardImg', value: response.uri });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'profileBizCardImg', value: response.uri });
|
||||
}
|
||||
|
||||
const body = {
|
||||
requests: [
|
||||
{
|
||||
image: {
|
||||
content: response.base64,
|
||||
},
|
||||
features: [
|
||||
{
|
||||
type: 'DOCUMENT_TEXT_DETECTION',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const visionResponse = await fetch(`https://vision.googleapis.com/v1/images:annotate?key=${privateKeys.googleapis.GOOGLE_API_KEY}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const visionParsed = await visionResponse.json();
|
||||
// Checks that some text was found before trying to read it
|
||||
if (visionParsed.responses[0].fullTextAnnotation && visionParsed.responses[0].fullTextAnnotation.text) {
|
||||
this.setState({
|
||||
text: visionParsed.responses[0].fullTextAnnotation.text,
|
||||
});
|
||||
console.log('STATE.TEXT\n', this.state.text);
|
||||
this.extractImageContext(this.state.text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts information from the OCR text and separates it based on context
|
||||
**/
|
||||
extractImageContext = async (imageText) => {
|
||||
const body = {
|
||||
encodingType: 'UTF8',
|
||||
document: {
|
||||
type: 'PLAIN_TEXT',
|
||||
content: imageText
|
||||
}
|
||||
};
|
||||
|
||||
const languageResponse = await fetch(`https://language.googleapis.com/v1/documents:analyzeEntities?key=${privateKeys.googleapis.GOOGLE_API_KEY}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const languageParsed = await languageResponse.json();
|
||||
// console.log('languageParsed,entities is\n', languageParsed.entities);
|
||||
this.populatePhoneField(imageText);
|
||||
this.extractFullAddress(imageText);
|
||||
|
||||
// Try fullAddress first
|
||||
if (regexObj.fullAddress) {
|
||||
this.populateFieldsFromFullAddress();
|
||||
}
|
||||
|
||||
this.performVisionParsing(languageParsed.entities);
|
||||
|
||||
if (!propsAlreadySet.street || !propsAlreadySet.city || !propsAlreadySet.region || !propsAlreadySet.postalCode) {
|
||||
this.extractSecondLine(imageText);
|
||||
this.populateAddressByLine(imageText);
|
||||
}
|
||||
|
||||
if (!propsAlreadySet.email) {
|
||||
this.extractEmail(imageText);
|
||||
}
|
||||
|
||||
if (!propsAlreadySet.website) {
|
||||
this.populateWebsiteField(imageText);
|
||||
}
|
||||
|
||||
if (!propsAlreadySet.company) {
|
||||
this.populateCompanyField();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates fields based on the common data assignment based on the data below
|
||||
* 0 - PROPER 1 - PERSON === NAME
|
||||
* 0 - PROPER 1 - ORGANIZATION === ADDRESS OR COMPANY
|
||||
* 0 - PROPER 1 - LOCATION === ADDRESS
|
||||
* 0 - PROPER 1 - OTHER === EMAIL OR WEBSITE
|
||||
* 0 - PROPER 1 - WORKOFART === ADDRESS
|
||||
* 0 - COMMON 1 - PERSON === TITLE
|
||||
**/
|
||||
performVisionParsing = (languageEntities) => {
|
||||
languageEntities.forEach((entity) => {
|
||||
for (let i = 0; i < entity.mentions.length; i++) {
|
||||
switch (entity.mentions[i].type) {
|
||||
case 'PROPER':
|
||||
// PROPER - PERSON is the name
|
||||
// We don't get the name from regex so just set it here
|
||||
if (entity.type === 'PERSON' && !propsAlreadySet.firstName) {
|
||||
this.populateNameField(entity.name);
|
||||
// PROPER - LOCATION is the address
|
||||
} else if (entity.type === 'LOCATION') {
|
||||
this.extractAddressFromVisionFields(entity.name);
|
||||
// PROPER - ORGANIZATION is the address or company
|
||||
} else if (entity.type === 'ORGANIZATION') {
|
||||
if (this.isAddressField(entity.name)) {
|
||||
this.extractAddressFromVisionFields(entity.name);
|
||||
} else {
|
||||
this.extractAddressOrCompany(entity.name);
|
||||
}
|
||||
// PROPER - WORKOFART is the address
|
||||
} else if (entity.type === 'WORKOFART') {
|
||||
this.extractAddressFromVisionFields(entity.name);
|
||||
// PROPER - OTHER is the email or website
|
||||
} else if (entity.type === 'OTHER') {
|
||||
this.extractEmailOrWebsite(entity.name);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'COMMON':
|
||||
// COMMON - PERSON is the jobTitle
|
||||
// We don't get the jobTitle from regex so just set it here
|
||||
if (entity.type === 'PERSON' && !propsAlreadySet.jobTitle) {
|
||||
propsAlreadySet.jobTitle = true;
|
||||
this.setState({ jobTitle: entity.name });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.combineTitleAndCompany();
|
||||
this.combineLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find phone number from the imageText.
|
||||
* Since phone isn't typically extracted from the
|
||||
* vision process we set the phone now
|
||||
**/
|
||||
populatePhoneField(imageText) {
|
||||
try {
|
||||
const phoneRegex = /\(?\d{3}\)?-?\.?\s?\d{3}-?.?\s?\d{4}/;
|
||||
let extractedPhone = '';
|
||||
|
||||
if (Array.isArray(imageText)) {
|
||||
extractedPhone = imageText[0].match(phoneRegex);
|
||||
} else {
|
||||
extractedPhone = imageText.match(phoneRegex);
|
||||
}
|
||||
|
||||
if (extractedPhone) {
|
||||
propsAlreadySet.primaryPhone = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'primaryPhone', value: extractedPhone[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'primaryPhone', value: extractedPhone[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not populate phone from '${imageText}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the firstName and lastName fields
|
||||
**/
|
||||
populateNameField(proposedName) {
|
||||
const nameArray = proposedName.split(' ');
|
||||
let lastNameStr = '';
|
||||
|
||||
switch (true) {
|
||||
case (nameArray.length > 3):
|
||||
return;
|
||||
case (nameArray.length === 3):
|
||||
lastNameStr = `${nameArray[1]} ${nameArray[2]}`;
|
||||
break;
|
||||
case (nameArray.length === 2):
|
||||
lastNameStr = nameArray[1];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
propsAlreadySet.firstName = true;
|
||||
let fullName = '';
|
||||
if (lastNameStr) {
|
||||
fullName = `${nameArray[0]} ${lastNameStr}`;
|
||||
} else {
|
||||
fullName = nameArray[0];
|
||||
}
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'firstName', value: nameArray[0] });
|
||||
this.props.contactUpdate({ prop: 'fullName', value: fullName });
|
||||
if (lastNameStr) {
|
||||
this.props.contactUpdate({ prop: 'lastName', value: lastNameStr });
|
||||
}
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'firstName', value: nameArray[0] });
|
||||
this.props.profileUpdate({ prop: 'fullName', value: fullName });
|
||||
if (lastNameStr) {
|
||||
this.props.profileUpdate({ prop: 'lastName', value: lastNameStr });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs regex and populates fields if found
|
||||
**/
|
||||
extractEmail(textToParse) {
|
||||
try {
|
||||
const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+/i;
|
||||
let extractedEmail = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedEmail = textToParse[0].match(emailRegex);
|
||||
} else {
|
||||
extractedEmail = textToParse.match(emailRegex);
|
||||
}
|
||||
|
||||
if (extractedEmail) {
|
||||
propsAlreadySet.email = true;
|
||||
regexObj.email = extractedEmail;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'contactEmail', value: extractedEmail[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'profileEmail', value: extractedEmail[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract email from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vision extraction has already been attempted
|
||||
* Extracts the website from image text first and if unsuccessful
|
||||
* then just get it from the email domain
|
||||
**/
|
||||
populateWebsiteField(imageText) {
|
||||
try {
|
||||
if (!propsAlreadySet.website) {
|
||||
this.extractWebsite(imageText);
|
||||
}
|
||||
if (!propsAlreadySet.website && propsAlreadySet.email) {
|
||||
this.extractWebsite(regexObj.email);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Could not populate website', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs regex and populates fields if found
|
||||
**/
|
||||
extractWebsite(textToParse) {
|
||||
try {
|
||||
const websiteRegex = /w?w?w?\.?[a-z-]{2,}\.[a-z]{2,}\.?[a-z]*/i;
|
||||
let extractedWebsite = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedWebsite = textToParse[0].match(websiteRegex);
|
||||
} else {
|
||||
extractedWebsite = textToParse.match(websiteRegex);
|
||||
}
|
||||
|
||||
if (extractedWebsite) {
|
||||
propsAlreadySet.website = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'website', value: extractedWebsite[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'website', value: extractedWebsite[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract website from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the company from vision first and from image text second
|
||||
* If both of those fail then just get it from the email domain
|
||||
**/
|
||||
populateCompanyField() {
|
||||
try {
|
||||
if (propsAlreadySet.company) {
|
||||
return;
|
||||
}
|
||||
if (visionObj.company) {
|
||||
this.extractCompanyFromVision();
|
||||
} else if (propsAlreadySet.email) {
|
||||
this.extractCompanyFromEmail();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Could not populate company', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the company from the vision response
|
||||
**/
|
||||
extractCompanyFromVision() {
|
||||
propsAlreadySet.company = true;
|
||||
this.setState({ company: visionObj.company });
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the company from the email
|
||||
**/
|
||||
extractCompanyFromEmail() {
|
||||
const { email } = regexObj;
|
||||
// Sets the company to be anything between the '@' and '.'
|
||||
let atIndex;
|
||||
let dotIndex;
|
||||
let foundAt = false;
|
||||
for (let i = 0; i < email.length; i++) {
|
||||
if (email[i] === '@') {
|
||||
atIndex = i + 1;
|
||||
foundAt = true;
|
||||
} else if (foundAt && email[i] === '.') {
|
||||
dotIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
const company = email.slice(atIndex, dotIndex);
|
||||
propsAlreadySet.company = true;
|
||||
this.setState({ company });
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the jobTitle and Company fields into one field for the details section
|
||||
**/
|
||||
combineTitleAndCompany() {
|
||||
const company = this.state.company || '';
|
||||
const jobTitle = this.state.jobTitle || '';
|
||||
if (!company && !jobTitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleAndCompany = `${jobTitle} ${company}`;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'titleAndCompany', value: titleAndCompany });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'titleAndCompany', value: titleAndCompany });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the region and state fields into one field for the details section
|
||||
**/
|
||||
combineLocation() {
|
||||
const city = this.state.city || '';
|
||||
const region = this.state.region || '';
|
||||
if (!city && !region) {
|
||||
return;
|
||||
}
|
||||
const location = region ? `${city}, ${region}` : city;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'location', value: location });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'location', value: location });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the full address for comparison to vision response
|
||||
**/
|
||||
extractFullAddress(imageText) {
|
||||
try {
|
||||
const fullAddressRegex = /\d+ [a-zA-Z,.# 0-9]+[\s |][a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const extractedFullAddress = imageText.match(fullAddressRegex);
|
||||
if (extractedFullAddress) {
|
||||
regexObj.fullAddress = extractedFullAddress;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract full address from '${imageText}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the first line of address for comparison to vision response
|
||||
**/
|
||||
extractFirstLine(textToParse) {
|
||||
try {
|
||||
regexObj.firstLine = '';
|
||||
const firstLineRegex = /\d+ [a-zA-Z.,# 0-9]+\s/;
|
||||
let extractedFirstLine = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedFirstLine = textToParse[0].match(firstLineRegex);
|
||||
} else {
|
||||
extractedFirstLine = textToParse.match(firstLineRegex);
|
||||
}
|
||||
|
||||
if (extractedFirstLine && !propsAlreadySet.street) {
|
||||
propsAlreadySet.street = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'street', value: extractedFirstLine[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'street', value: extractedFirstLine[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract first line of address from'${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the second line of address for comparison to vision response
|
||||
**/
|
||||
extractSecondLine(textToParse) {
|
||||
try {
|
||||
regexObj.secondLine = '';
|
||||
const secondLineRegex = /[a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
let extractedSecondLine = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedSecondLine = textToParse[0].match(secondLineRegex);
|
||||
} else {
|
||||
extractedSecondLine = textToParse.match(secondLineRegex);
|
||||
}
|
||||
|
||||
if (extractedSecondLine) {
|
||||
regexObj.secondLine = extractedSecondLine[0];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log(`Could not extract second line of address from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the city
|
||||
**/
|
||||
extractCity(textToParse) {
|
||||
try {
|
||||
regexObj.city = '';
|
||||
const cityRegex = /[a-zA-Z ]+,/;
|
||||
let extractedCity = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedCity = textToParse[0].match(cityRegex);
|
||||
} else {
|
||||
extractedCity = textToParse.match(cityRegex);
|
||||
}
|
||||
|
||||
if (extractedCity) {
|
||||
extractedCity = extractedCity[0].replace(/,/, '');
|
||||
propsAlreadySet.city = true;
|
||||
this.setState({ city: extractedCity });
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'city', value: extractedCity });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'city', value: extractedCity });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract city from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the region (state)
|
||||
**/
|
||||
extractRegion(textToParse) {
|
||||
try {
|
||||
regexObj.region = '';
|
||||
const regionRegex = /, [A-Z]{2}/;
|
||||
let extractedRegion = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedRegion = textToParse[0].match(regionRegex);
|
||||
} else {
|
||||
extractedRegion = textToParse.match(regionRegex);
|
||||
}
|
||||
|
||||
if (extractedRegion) {
|
||||
extractedRegion = extractedRegion[0].replace(/, /, '');
|
||||
propsAlreadySet.region = true;
|
||||
this.setState({ region: extractedRegion });
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'region', value: extractedRegion });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'region', value: extractedRegion });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract region from ${textToParse}' ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the postalCode
|
||||
**/
|
||||
extractPostalCode(textToParse) {
|
||||
try {
|
||||
regexObj.postalCode = '';
|
||||
const zipRegex = /\d{5}/;
|
||||
let extractedPostalCode = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedPostalCode = textToParse[0].match(zipRegex);
|
||||
} else {
|
||||
extractedPostalCode = textToParse.match(zipRegex);
|
||||
}
|
||||
|
||||
if (extractedPostalCode) {
|
||||
propsAlreadySet.postalCode = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'postalCode', value: extractedPostalCode[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'postalCode', value: extractedPostalCode[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract postalCode from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a field is an address field of some form
|
||||
**/
|
||||
isAddressField(textToParse) {
|
||||
const fullAddressRegex = /\d+ [a-zA-Z,.# 0-9]+[\s |][a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const foundFullAddress = textToParse.match(fullAddressRegex);
|
||||
|
||||
const firstLineRegex = /\d+ [a-zA-Z.,# 0-9]+\s/;
|
||||
const foundFirstLine = textToParse.toString().match(firstLineRegex);
|
||||
|
||||
const secondLineRegex = /[a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const foundSecondLine = textToParse.match(secondLineRegex);
|
||||
|
||||
const cityRegex = /[a-zA-Z ]+,/;
|
||||
const foundCity = textToParse.match(cityRegex);
|
||||
|
||||
const regionRegex = /, [A-Z]{2}/;
|
||||
const foundRegion = textToParse.match(regionRegex);
|
||||
|
||||
const zipRegex = /\d{5}/;
|
||||
const foundPostalCode = textToParse.match(zipRegex);
|
||||
|
||||
return foundFullAddress || foundFirstLine || foundSecondLine || foundCity || foundRegion || foundPostalCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the regex for full address is so strict if we found something that
|
||||
* likely means we have the full address so we'll just use that and not
|
||||
* look at the vision response since address isn't that reliable
|
||||
**/
|
||||
populateFieldsFromFullAddress() {
|
||||
// Populate the street
|
||||
this.extractFirstLine(regexObj.fullAddress[0]);
|
||||
|
||||
// Get the second line then split into city, region, and zip
|
||||
this.extractSecondLine(regexObj.fullAddress[0]);
|
||||
if (regexObj.secondLine) {
|
||||
// Populate the city, region, and postalCode fields
|
||||
this.extractCity(regexObj.secondLine);
|
||||
this.extractRegion(regexObj.secondLine);
|
||||
this.extractPostalCode(regexObj.secondLine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the vision response to find remaining address fields
|
||||
**/
|
||||
extractAddressFromVisionFields(textToParse) {
|
||||
// Check for Street if not found
|
||||
if (!propsAlreadySet.street) {
|
||||
this.extractFirstLine(textToParse);
|
||||
}
|
||||
|
||||
// Check for city if not found
|
||||
if (!propsAlreadySet.city) {
|
||||
this.extractCity(textToParse);
|
||||
}
|
||||
|
||||
// Check for region if not found
|
||||
if (!propsAlreadySet.region) {
|
||||
this.extractRegion(textToParse);
|
||||
}
|
||||
|
||||
// Check for postalCode if not found
|
||||
if (!propsAlreadySet.postalCode) {
|
||||
this.extractPostalCode(textToParse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PROPER - ORGANIZATION is address or company
|
||||
* This tries to determine which one it is
|
||||
**/
|
||||
extractAddressOrCompany(textToParse) {
|
||||
const firstLineRegex = /\d+ [a-zA-Z.,# 0-9]+\s/;
|
||||
const extractedFirstLine = textToParse.toString().match(firstLineRegex);
|
||||
|
||||
const secondLineRegex = /[a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const extractedSecondLine = textToParse.match(secondLineRegex);
|
||||
|
||||
// If we didn't find either address line then assume it's company
|
||||
if (!extractedFirstLine && !extractedSecondLine && !propsAlreadySet.company) {
|
||||
visionObj.company = textToParse;
|
||||
this.populateCompanyField();
|
||||
}
|
||||
// If we found either line then extract the info from it
|
||||
if (extractedFirstLine || extractedSecondLine) {
|
||||
this.populateAddressByLine(textToParse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PROPER - OTHER is email or website
|
||||
* This tries to determine which one it is
|
||||
**/
|
||||
extractEmailOrWebsite(textToParse) {
|
||||
if (textToParse.indexOf('@') > 1) {
|
||||
if (!propsAlreadySet.email) {
|
||||
this.extractEmail(textToParse);
|
||||
}
|
||||
} else if (!propsAlreadySet.website) {
|
||||
this.extractWebsite(textToParse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the remaining fields and try to populate them using regex
|
||||
**/
|
||||
populateAddressByLine(textToParse) {
|
||||
const { secondLine } = regexObj;
|
||||
if (!propsAlreadySet.street) {
|
||||
this.extractFirstLine(textToParse);
|
||||
}
|
||||
if (secondLine) {
|
||||
if (!propsAlreadySet.city) {
|
||||
this.extractCity(secondLine);
|
||||
}
|
||||
if (!propsAlreadySet.region) {
|
||||
this.extractRegion(secondLine);
|
||||
}
|
||||
if (!propsAlreadySet.postalCode) {
|
||||
this.extractPostalCode(secondLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the business card tile
|
||||
**/
|
||||
renderBusinessCardTile() {
|
||||
let base64 = '';
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
base64 = this.props.contactBizCardImg;
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
base64 = this.props.profileBizCardImg;
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
base64 = this.props.socialBizCardImg;
|
||||
}
|
||||
if (base64) {
|
||||
return (
|
||||
<Tile
|
||||
imageSrc={{ uri: base64 }}
|
||||
featured
|
||||
containerStyle={styles.tileContainerStyle}
|
||||
imageContainerStyle={styles.tileStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tile
|
||||
featured
|
||||
title={strings('bizCard.photoTile_noCard')}
|
||||
containerStyle={styles.tileContainerStyle}
|
||||
imageContainerStyle={styles.tileStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the photo buttons
|
||||
**/
|
||||
renderPhotoButtons() {
|
||||
if (this.props.formType === SOCIAL_FORM || !this.props.isEditMode) {
|
||||
return <View style={styles.buttonContainerStyle} />;
|
||||
}
|
||||
return (
|
||||
<View style={styles.buttonContainerStyle}>
|
||||
<Button
|
||||
onPress={this.onPickImageButtonPress.bind(this)}
|
||||
title={strings('common.buttons.uploadImage')}
|
||||
buttonStyle={styles.halfButtonStyle}
|
||||
disabled={this.props.loading}
|
||||
icon={{ name: 'folder' }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onPress={this.onTakePhotoButtonPress.bind(this)}
|
||||
title={strings('common.buttons.takePhoto')}
|
||||
buttonStyle={styles.halfButtonStyle}
|
||||
disabled={this.props.loading}
|
||||
icon={{ name: 'photo-camera' }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
if (this.props.isEditMode) {
|
||||
return (
|
||||
<View style={styles.viewStyle}>
|
||||
{this.renderBusinessCardTile()}
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.viewStyle}>
|
||||
<Spacer space={45} />
|
||||
{this.renderBusinessCardTile()}
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
tileContainerStyle: {
|
||||
height,
|
||||
width,
|
||||
marginTop: 15,
|
||||
marginBottom: 15
|
||||
},
|
||||
tileStyle: {
|
||||
height,
|
||||
width
|
||||
},
|
||||
buttonContainerStyle: {
|
||||
flex: 2,
|
||||
marginTop: 15,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
halfButtonStyle: {
|
||||
backgroundColor: colors.buttonBlue,
|
||||
flex: 1,
|
||||
borderRadius: 5
|
||||
},
|
||||
viewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Map State To Props
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
profileBizCardImg,
|
||||
} = state.profileForm;
|
||||
|
||||
const {
|
||||
contactBizCardImg,
|
||||
} = state.contactForm;
|
||||
|
||||
const {
|
||||
socialBizCardImg,
|
||||
} = state.socialContacts;
|
||||
|
||||
return { profileBizCardImg, contactBizCardImg, socialBizCardImg };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
contactUpdate, profileUpdate
|
||||
})(BizCard);
|
||||
@ -1,23 +0,0 @@
|
||||
/** Used as a placeholder with an empty title for the drawerNavigator reset action from the welcomScreen **/
|
||||
import React, { Component } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
class EmptyNavigator extends Component {
|
||||
static navigationOptions = {
|
||||
title: '',
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<Text>
|
||||
{strings('common.error.navigatedToEmpty')}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmptyNavigator;
|
||||
@ -1,5 +0,0 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const NEAR_YOU = 'near_you';
|
||||
export const THIS_WEEK = 'this_week';
|
||||
export const TODAY = 'today';
|
||||
export const TOMORROW = 'tomorrow';
|
||||
@ -1,4 +0,0 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const THIS_WEEK = 'this_week';
|
||||
export const TODAY = 'today';
|
||||
export const TOMORROW = 'tomorrow';
|
||||
@ -1,6 +0,0 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const PROFILE_FORM = 'profile';
|
||||
export const CONTACT_FORM = 'contact';
|
||||
export const EVENT_FORM = 'event';
|
||||
export const SOCIAL_FORM = 'social';
|
||||
export const TASK_FORM = 'task';
|
||||
@ -1,5 +0,0 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const REFERRAL_RECEIVED = 'referralReceived';
|
||||
export const REQUEST_ACCEPTED = 'requestAccepted';
|
||||
export const REQUEST_RECEIVED = 'requestReceived';
|
||||
export const REQUEST_SENT = 'requestSent';
|
||||
@ -1,866 +0,0 @@
|
||||
/** Contains the base fields for a contact reuse **/
|
||||
import _ from 'lodash';
|
||||
import { ImagePicker, MailComposer, Permissions } from 'expo';
|
||||
import React, { Component } from 'react';
|
||||
import { Alert, Dimensions, Image, Linking, Modal, ScrollView, Share, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { Button, Icon } from 'react-native-elements';
|
||||
import ModalDropdown from 'react-native-modal-dropdown';
|
||||
import { connect } from 'react-redux';
|
||||
import { contactUpdate, profileToggleDisabled, profileSave, profileUpdate, socialRemoveFriend } from '../actions';
|
||||
import { PROFILE_FORM, CONTACT_FORM, SOCIAL_FORM } from './FormTypes';
|
||||
import { Confirm, Spacer } from './common';
|
||||
import colors from '../config/colors.json';
|
||||
import privateKeys from '../../private_keys.json';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
const GREEN_CHECK_IMAGE = require('../../assets/GreenCheck.png');
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const DEV_LINKING_BASE_URL = privateKeys.deepLinking.devBaseUrl;
|
||||
const TAGFER_BASE_URL = privateKeys.deepLinking.tagferBaseUrl;
|
||||
const profileKebabOptions = [
|
||||
strings('profile.kebabOptions_shareQR'),
|
||||
strings('profile.kebabOptions_shareLink'),
|
||||
strings('profile.kebabOptions_edit'),
|
||||
strings('profile.kebabOptions_duplicate'),
|
||||
strings('profile.kebabOptions_disable')
|
||||
];
|
||||
const friendKebabOptions = [
|
||||
strings('profile.kebabOptions_shareQR'),
|
||||
strings('profile.kebabOptions_shareLink'),
|
||||
// strings('profile.kebabOptions_unfollow'),
|
||||
strings('profile.kebabOptions_merge'),
|
||||
strings('profile.kebabOptions_remove'),
|
||||
strings('profile.kebabOptions_report')
|
||||
];
|
||||
let profileNames = [];
|
||||
let copyFromIndex = 0;
|
||||
let copyToIndex = 0;
|
||||
|
||||
class ProfileAvatar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
cameraRollStatus: '',
|
||||
isDuplicateModalVisible: false,
|
||||
isDisableModaleVisible: false,
|
||||
isRemoveModalVisible: false
|
||||
};
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
this.populateKebabLastOption();
|
||||
profileNames = [];
|
||||
for (let i = 0; i < this.props.profiles.length; i++) {
|
||||
profileNames.push(this.props.profiles[i].profileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the profile data from the copyFromIndex to the copyToIndex profile
|
||||
**/
|
||||
onDuplicateButtonPress() {
|
||||
if (copyFromIndex === copyToIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
profileBizCardImg,
|
||||
profileAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
titleAndCompany,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
profileEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
isDisabled,
|
||||
} = this.props.profiles[copyFromIndex];
|
||||
|
||||
const { profileName, uid } = this.props.profiles[copyToIndex];
|
||||
|
||||
this.props.profileSave({
|
||||
profileBizCardImg,
|
||||
profileAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
titleAndCompany,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
profileEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
profileName,
|
||||
isDisabled,
|
||||
uid // DON'T DELETE THIS PROPERTY WHEN UPDATING LIST
|
||||
});
|
||||
this.setState({ isDuplicateModalVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends user to the gallery to upload image from device
|
||||
**/
|
||||
onPickImageButtonPress = async () => {
|
||||
if (this.state.cameraRollStatus !== 'granted') {
|
||||
await Permissions.askAsync(Permissions.CAMERA_ROLL)
|
||||
.then((data) => {
|
||||
if (data.status !== 'granted') {
|
||||
Alert.alert(strings('common.error.permissionDenied_cameraRoll'));
|
||||
Linking.openURL('app-settings:');
|
||||
return;
|
||||
}
|
||||
}).catch(err => console.error('askAsync camera roll error ', err));
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [3.5, 2],
|
||||
base64: true,
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result.uri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the camera for the user to take a new photo
|
||||
**/
|
||||
onTakePhotoButtonPress = async () => {
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
base64: true
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result.uri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disables the profile
|
||||
**/
|
||||
onDisableAccept() {
|
||||
this.toggleProfileEnabled();
|
||||
this.setState({ isDisableModaleVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the modal
|
||||
**/
|
||||
onDisableDecline() {
|
||||
this.setState({ isDisableModaleVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the friend upon button press
|
||||
**/
|
||||
onRemoveAccept() {
|
||||
const { myFriendId, userId, otherFriendId } = this.props.navigation.state.params;
|
||||
this.props.socialRemoveFriend({ myFriendId, userId, otherFriendId });
|
||||
this.setState({ isRemoveModalVisible: false });
|
||||
this.props.navigation.goBack();
|
||||
this.props.friendsFetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents friend removal and closes modal upon button press
|
||||
**/
|
||||
onRemoveDecline() {
|
||||
this.setState({ isRemoveModalVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current permission state of the app for camera roll/photos
|
||||
**/
|
||||
setCameraRollStatus = async () => {
|
||||
const { status } = await Permissions.getAsync(Permissions.CAMERA_ROLL);
|
||||
this.setState({ cameraRollStatus: status });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether or not the profile is enabled
|
||||
**/
|
||||
async toggleProfileEnabled() {
|
||||
this.props.profileToggleDisabled({ isDisabled: !this.props.isDisabled, uid: this.props.uid });
|
||||
await this.props.profileUpdate({ prop: 'isDisabled', value: !this.props.isDisabled });
|
||||
this.populateKebabLastOption();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the chosen image in base64 and saves the base64
|
||||
**/
|
||||
processImage = async (uri) => {
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'contactAvatarImg', value: uri });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'profileAvatarImg', value: uri });
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
this.props.profileUpdate({ prop: 'socialAvatarImg', value: uri });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates last option of the kebab menu based on disabled or not
|
||||
**/
|
||||
populateKebabLastOption() {
|
||||
profileKebabOptions.pop();
|
||||
profileKebabOptions.push(this.props.isDisabled ? strings('profile.kebabOptions_enable') : strings('profile.kebabOptions_disable'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an action based on the option selected from the kebab menu
|
||||
**/
|
||||
async processProfileKebabSelection(idx) {
|
||||
const { profileAvatarImg, fullName, titleAndCompany, tagferId, uid } = this.props;
|
||||
switch (idx) {
|
||||
case '0':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.props.navigation.navigate('shareProfileQRCodeScreen', { profileAvatarImg, fullName, titleAndCompany, tagferId, uid });
|
||||
break;
|
||||
|
||||
case '1':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.shareLink(tagferId, uid);
|
||||
break;
|
||||
|
||||
case '2':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.props.navigation.navigate('profileEdit');
|
||||
break;
|
||||
|
||||
case '3':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.setState({ isDuplicateModalVisible: true });
|
||||
break;
|
||||
|
||||
case '4':
|
||||
if (this.props.isDisabled) {
|
||||
this.toggleProfileEnabled();
|
||||
} else {
|
||||
this.setState({ isDisableModaleVisible: true });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an action based on the option selected from the kebab menu
|
||||
**/
|
||||
async processSocialKebabSelection(idx) {
|
||||
const { profileAvatarImg, fullName, titleAndCompany, otherTagferId, otherUserId, otherProfileId, myFriendId } = this.props.navigation.state.params;
|
||||
switch (idx) {
|
||||
case '0':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.props.navigation.navigate('shareProfileQRCodeScreen', { profileAvatarImg, fullName, titleAndCompany, otherTagferId, otherUserId });
|
||||
break;
|
||||
|
||||
case '1':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.shareLink(otherTagferId, otherUserId);
|
||||
break;
|
||||
|
||||
case '2':
|
||||
this.props.navigation.navigate('socialMergeScreen', { fullName, titleAndCompany, otherTagferId, otherUserId, myFriendId });
|
||||
break;
|
||||
|
||||
case '3':
|
||||
this.setState({ isRemoveModalVisible: !this.state.isRemoveModalVisible });
|
||||
break;
|
||||
|
||||
case '4':
|
||||
MailComposer.composeAsync({
|
||||
recipients: ['abuse@tagfer.com'],
|
||||
subject: strings('common.forms.reportContact_emailHeader'),
|
||||
body: strings('common.forms.reportContact_emailMessage', { otherTagferId, otherProfileId })
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the normal hardware process for sharing something via social, sms, etc
|
||||
* uid - the uid for the profile to be shared
|
||||
**/
|
||||
shareLink(tagferId, uid) {
|
||||
Share.share(
|
||||
{
|
||||
message: strings('common.linking.shareProfile_body', { tagferId, DEV_LINKING_BASE_URL, uid }),
|
||||
title: strings('common.linking.shareProfile_title'),
|
||||
url: `${TAGFER_BASE_URL}${tagferId}/${uid}`
|
||||
},
|
||||
{
|
||||
tintColor: 0x056ecf,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.PostToWeibo',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.CopyToPasteboard',
|
||||
'com.apple.UIKit.activity.AssignToContact',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.AddToReadingList',
|
||||
'com.apple.UIKit.activity.PostToFlickr',
|
||||
'com.apple.UIKit.activity.PostToVimeo',
|
||||
'com.apple.UIKit.activity.PostToTencentWeibo',
|
||||
'com.apple.UIKit.activity.AirDrop',
|
||||
'com.apple.UIKit.activity.OpenInIBooks',
|
||||
'com.apple.UIKit.activity.MarkupAsPDF',
|
||||
'com.apple.reminders.RemindersEditorExtension', //Reminders
|
||||
'com.apple.mobilenotes.SharingExtension', // Notes
|
||||
'com.apple.mobileslideshow.StreamShareService', // iCloud Photo Sharing - This also does nothing :{
|
||||
|
||||
// Not supported
|
||||
'com.linkedin.LinkedIn.ShareExtension', //LinkedIn
|
||||
'pinterest.ShareExtension', //Pinterest
|
||||
'com.google.GooglePlus.ShareExtension', //Google +
|
||||
'com.tumblr.tumblr.Share-With-Tumblr', //Tumblr
|
||||
'wefwef.YammerShare', //Yammer
|
||||
'com.hootsuite.hootsuite.HootsuiteShareExt', //HootSuite
|
||||
'net.naan.TwitterFonPro.ShareExtension-Pro', //Echofon
|
||||
'net.whatsapp.WhatsApp.ShareExtension' //WhatsApp
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar cirle on profiles
|
||||
**/
|
||||
renderAvatarPhoto() {
|
||||
let base64 = '';
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
base64 = this.props.contactAvatarImg;
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
base64 = this.props.profileAvatarImg;
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
base64 = this.props.socialAvatarImg;
|
||||
}
|
||||
if (base64) {
|
||||
if (this.props.isEditMode) {
|
||||
return (
|
||||
<View style={styles.avatarEditStyle}>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarStyle}
|
||||
/>
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Not edit mode but has avatar image
|
||||
return (
|
||||
<View>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// No profile Image
|
||||
if (this.props.isEditMode) {
|
||||
return (
|
||||
<View style={styles.noAvatarEditStyle}>
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={150}
|
||||
color={colors.lightTeal}
|
||||
/>
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={150}
|
||||
color={colors.lightTeal}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the photo buttons
|
||||
**/
|
||||
renderPhotoButtons() {
|
||||
if (this.props.formType === SOCIAL_FORM) {
|
||||
return <View style={styles.buttonContainerStyle} />;
|
||||
}
|
||||
return (
|
||||
<View style={styles.buttonContainerStyle}>
|
||||
<TouchableOpacity
|
||||
onPress={this.onPickImageButtonPress.bind(this)}
|
||||
disabled={this.props.loading}
|
||||
>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.uploadImage')}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={styles.photoButtonsOrTextStyle}>{strings('profile.or')}</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={this.onTakePhotoButtonPress.bind(this)}
|
||||
disabled={this.props.loading}
|
||||
>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.takePhoto')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an icon on the right of the profile row if it is the selected profile
|
||||
**/
|
||||
renderHightlightedIcon(highlighted) {
|
||||
return (
|
||||
<Image
|
||||
style={styles.highlightedImageStyle}
|
||||
mode='stretch'
|
||||
source={highlighted ? GREEN_CHECK_IMAGE : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an icon to the left of the profile name in the profile row
|
||||
**/
|
||||
renderLeftIcon(rowID) {
|
||||
const base64 = this.props.profiles[rowID].profileAvatarImg;
|
||||
|
||||
if (base64) {
|
||||
return (
|
||||
<View style={styles.leftIconStyle}>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarSmallImageStyle}
|
||||
/>
|
||||
<Text style={[styles.modalDropdownDropdownTextStyle, { color: this.props.profiles[rowID].isDisabled ? colors.lightGrey : colors.black }]}>
|
||||
{profileNames[rowID]}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.leftIconStyle}>
|
||||
<Icon
|
||||
name={'account-circle'}
|
||||
size={30}
|
||||
color={colors.middleGrey}
|
||||
/>
|
||||
<Text style={[styles.modalDropdownDropdownTextStyle, { color: this.props.profiles[rowID].isDisabled ? colors.lightGrey : colors.black }]}>
|
||||
{profileNames[rowID]}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for custom styling of the modal dropdown
|
||||
**/
|
||||
renderDuplicateModalDropdownRow(rowData, rowID, highlighted) {
|
||||
return (
|
||||
<View style={{ backgroundColor: this.props.profiles[rowID].isDisabled ? colors.offWhite : colors.white }}>
|
||||
<View style={styles.modalDropdownDropdownViewStyle}>
|
||||
{this.renderLeftIcon(rowID)}
|
||||
|
||||
{this.renderHightlightedIcon(highlighted)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the kebab menu
|
||||
**/
|
||||
renderKebab() {
|
||||
if (!this.props.isEditMode) {
|
||||
if (this.props.formType === PROFILE_FORM) {
|
||||
return (
|
||||
<View style={styles.kebabViewStyle}>
|
||||
<ModalDropdown
|
||||
style={styles.modalDropdownStyle}
|
||||
dropdownStyle={styles.profileModalDropsdownDropdownStyle}
|
||||
dropdownTextStyle={styles.modalDropsdownDropdownTextStyle}
|
||||
options={profileKebabOptions}
|
||||
onSelect={(idx) => this.processProfileKebabSelection(idx)}
|
||||
>
|
||||
<Icon
|
||||
name={'more-vert'}
|
||||
size={40}
|
||||
containerStyle={{ paddingLeft: 10, paddingTop: 10 }}
|
||||
/>
|
||||
</ModalDropdown>
|
||||
</View>
|
||||
);
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
return (
|
||||
<View style={styles.kebabViewStyle}>
|
||||
<ModalDropdown
|
||||
style={styles.modalDropdownStyle}
|
||||
dropdownStyle={styles.socialModalDropdownDropdownStyle}
|
||||
dropdownTextStyle={styles.modalDropsdownDropdownTextStyle}
|
||||
options={friendKebabOptions}
|
||||
onSelect={(idx) => this.processSocialKebabSelection(idx)}
|
||||
>
|
||||
<Icon
|
||||
name={'more-vert'}
|
||||
size={40}
|
||||
containerStyle={{ paddingLeft: 10, paddingTop: 10 }}
|
||||
/>
|
||||
</ModalDropdown>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const name = `${this.props.fullName}`;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.avatarViewStyle}>
|
||||
{this.renderAvatarPhoto()}
|
||||
</View>
|
||||
{this.renderKebab()}
|
||||
|
||||
<Modal
|
||||
transparent
|
||||
animationType="slide"
|
||||
visible={this.state.isDuplicateModalVisible}
|
||||
onRequestClose={() => {}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.modalOpacityStyle}
|
||||
activeOpacity={1}
|
||||
onPressOut={() => this.setState({ isDuplicateModalVisible: false })}
|
||||
>
|
||||
<ScrollView
|
||||
directionalLockEnabled
|
||||
centerContent
|
||||
contentContainerStyle={styles.modalScrollViewStyle}
|
||||
>
|
||||
<TouchableWithoutFeedback>
|
||||
<View>
|
||||
<View style={styles.duplicateMainViewStyle}>
|
||||
<View />
|
||||
<View>
|
||||
<Spacer />
|
||||
<Text style={styles.leftAlignedTextStyle}>{strings('profile.duplicateScreen_duplicateBasicInfo')}</Text>
|
||||
<Spacer />
|
||||
<Text style={styles.leftAlignedTextStyle}>{strings('profile.duplicateScreen_duplicateFrom')}</Text>
|
||||
<Spacer />
|
||||
<ModalDropdown
|
||||
style={styles.duplicateModalDropdownStyle}
|
||||
dropdownStyle={styles.modalDropdownDropdownStyle}
|
||||
options={profileNames}
|
||||
renderRow={this.renderDuplicateModalDropdownRow.bind(this)}
|
||||
onSelect={(idx) => {
|
||||
copyFromIndex = idx;
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Spacer />
|
||||
<Text style={styles.leftAlignedTextStyle}>{strings('profile.duplicateScreen_duplicateTo')}</Text>
|
||||
<Spacer />
|
||||
<ModalDropdown
|
||||
style={styles.duplicateModalDropdownStyle}
|
||||
dropdownStyle={styles.modalDropdownDropdownStyle}
|
||||
options={profileNames}
|
||||
renderRow={this.renderDuplicateModalDropdownRow.bind(this)}
|
||||
onSelect={(idx) => {
|
||||
copyToIndex = idx;
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
onPress={() => this.onDuplicateButtonPress()}
|
||||
title={strings('common.buttons.duplicate')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
fontWeight="bold"
|
||||
fontSize={25}
|
||||
/>
|
||||
<Spacer />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
|
||||
<Confirm
|
||||
visible={this.state.isDisableModaleVisible}
|
||||
onAccept={this.onDisableAccept.bind(this)}
|
||||
onDecline={this.onDisableDecline.bind(this)}
|
||||
>
|
||||
{strings('profile.kebabOptions_disableInfo')}
|
||||
</Confirm>
|
||||
|
||||
<Confirm
|
||||
visible={this.state.isRemoveModalVisible}
|
||||
onAccept={this.onRemoveAccept.bind(this)}
|
||||
onDecline={this.onRemoveDecline.bind(this)}
|
||||
>
|
||||
{strings('common.modal.confirm_delete', { name })}
|
||||
</Confirm>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
buttonContainerStyle: {
|
||||
flex: 2,
|
||||
marginTop: 15,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
textButtonStyle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: colors.labelBlue,
|
||||
marginTop: 5,
|
||||
marginBottom: 5,
|
||||
marginLeft: 15
|
||||
},
|
||||
photoButtonsOrTextStyle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: colors.lightGrey,
|
||||
marginTop: 5,
|
||||
marginBottom: 5,
|
||||
marginLeft: 15
|
||||
},
|
||||
kebabViewStyle: {
|
||||
position: 'absolute'
|
||||
},
|
||||
modalDropdownStyle: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
profileModalDropsdownDropdownStyle: {
|
||||
width: SCREEN_WIDTH * 0.6,
|
||||
height: 215,
|
||||
borderColor: colors.lightgrey,
|
||||
borderWidth: 1,
|
||||
marginLeft: 40,
|
||||
marginTop: -30
|
||||
},
|
||||
socialModalDropdownDropdownStyle: {
|
||||
width: SCREEN_WIDTH * 0.6,
|
||||
height: 210,
|
||||
borderColor: colors.lightgrey,
|
||||
borderWidth: 1,
|
||||
marginLeft: 40,
|
||||
marginTop: -30
|
||||
},
|
||||
modalDropsdownDropdownTextStyle: {
|
||||
fontSize: 18
|
||||
},
|
||||
modalTextStyle: {
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
lineHeight: 40
|
||||
},
|
||||
avatarViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 10
|
||||
},
|
||||
avatarEditStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 25
|
||||
},
|
||||
noAvatarEditStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10
|
||||
},
|
||||
avatarStyle: {
|
||||
width: 125,
|
||||
height: 125,
|
||||
borderWidth: 1,
|
||||
borderRadius: 60,
|
||||
marginTop: 15
|
||||
},
|
||||
modalOpacityStyle: {
|
||||
backgroundColor: colors.modalTransparency,
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
modalScrollViewStyle: {
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
modalCardStyle: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
duplicateMainViewStyle: {
|
||||
backgroundColor: colors.white,
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
paddingRight: 10
|
||||
},
|
||||
leftAlignedTextStyle: {
|
||||
textAlign: 'left',
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
fontSize: 15
|
||||
},
|
||||
duplicateModalDropdownStyle: {
|
||||
marginLeft: 20
|
||||
},
|
||||
modalDropdownDropdownViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
paddingLeft: 10
|
||||
},
|
||||
leftIconStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start'
|
||||
},
|
||||
modalDropdownDropdownStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
borderColor: colors.lightGrey,
|
||||
borderWidth: 1,
|
||||
marginLeft: -10
|
||||
},
|
||||
modalDropdownDropdownTextStyle: {
|
||||
fontSize: 18,
|
||||
marginLeft: 10
|
||||
},
|
||||
highlightedImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
marginRight: 10
|
||||
},
|
||||
avatarSmallImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderWidth: 1,
|
||||
borderRadius: 15
|
||||
},
|
||||
buttonStyle: {
|
||||
width: SCREEN_WIDTH * 0.9,
|
||||
marginTop: 15,
|
||||
marginBottom: 30,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.buttonBlue
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Map State To Props
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
// converts Profile objects into profile arrays
|
||||
const profiles = _.map(state.profiles, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
const {
|
||||
contactAvatarImg,
|
||||
} = state.contactForm;
|
||||
|
||||
const {
|
||||
socialAvatarImg
|
||||
} = state.socialContacts;
|
||||
|
||||
const {
|
||||
isDisabled,
|
||||
profileAvatarImg,
|
||||
uid
|
||||
} = state.profileForm;
|
||||
|
||||
return {
|
||||
profiles,
|
||||
contactAvatarImg,
|
||||
socialAvatarImg,
|
||||
isDisabled,
|
||||
profileAvatarImg,
|
||||
uid
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
contactUpdate, profileToggleDisabled, profileSave, profileUpdate, socialRemoveFriend
|
||||
})(ProfileAvatar);
|
||||
@ -1,6 +0,0 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const EVENT_TRANSACTION = 'event';
|
||||
export const EARNED_TRANSACTION = 'earned';
|
||||
export const RECEIVED_TRANSACTION = 'received';
|
||||
export const REQUESTED_TRANSACTION = 'requested';
|
||||
export const SENT_TRANSACTION = 'sent';
|
||||
@ -1,57 +0,0 @@
|
||||
/** Used for handling logging out a user **/
|
||||
import React, { Component } from 'react';
|
||||
import { AsyncStorage } from 'react-native';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
import { purgeStoredState } from 'redux-persist';
|
||||
import firebase from 'firebase';
|
||||
import { connect } from 'react-redux';
|
||||
import { Spinner } from '../components/common';
|
||||
import { profileListReset, propertiesReset, userFormReset } from '../actions';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
class UserLogout extends Component {
|
||||
static navigationOptions = {
|
||||
title: strings('logout.title'),
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
async componentWillMount() {
|
||||
firebase.auth().signOut();
|
||||
this.props.userFormReset();
|
||||
this.props.propertiesReset();
|
||||
this.props.profileListReset();
|
||||
purgeStoredState({ storage: AsyncStorage });
|
||||
this.navigateToLoginScreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to login screen and resets the navigation so no back option
|
||||
**/
|
||||
navigateToLoginScreen() {
|
||||
const resetAction = NavigationActions.reset({
|
||||
index: 0,
|
||||
actions: [
|
||||
NavigationActions.navigate({ routeName: 'userLogin' })
|
||||
]
|
||||
});
|
||||
|
||||
this.props.navigation.dispatch(resetAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<Spinner />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, {
|
||||
profileListReset,
|
||||
propertiesReset,
|
||||
userFormReset
|
||||
})(UserLogout);
|
||||
@ -1,498 +0,0 @@
|
||||
/** Contains the base fields for a contact reuse **/
|
||||
import React, { Component } from 'react';
|
||||
import { Animated, Dimensions, Platform, ScrollView, View } from 'react-native';
|
||||
import { Divider, FormInput } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { contactFormReset, contactUpdate } from '../../actions';
|
||||
import BizCard from '../BizCard';
|
||||
import ProfileAvatar from '../ProfileAvatar';
|
||||
import { FormMessage, FormLabel, Spacer } from '../common';
|
||||
import { CONTACT_FORM } from '../FormTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const DOTS_SIZE = ['first', 'second'];
|
||||
const scrollX = new Animated.Value(0);
|
||||
|
||||
class ContactForm extends Component {
|
||||
/**
|
||||
* Component Will Unmount
|
||||
**/
|
||||
componentWillUnmount() {
|
||||
this.props.contactFormReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Breaks the fullName into a first and last name for contact saving
|
||||
**/
|
||||
splitNameField(value) {
|
||||
this.props.contactUpdate({ prop: 'fullName', value });
|
||||
|
||||
const nameArr = this.props.fullName.split(' ');
|
||||
const firstName = nameArr[0];
|
||||
this.props.contactUpdate({ prop: 'firstName', value: firstName });
|
||||
|
||||
if (nameArr.length > 0) {
|
||||
const lastName = nameArr[1];
|
||||
this.props.contactUpdate({ prop: 'lastName', value: lastName });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar and biz card slider
|
||||
**/
|
||||
renderAvatarBizCardSlider() {
|
||||
const position = Animated.divide(scrollX, SCREEN_WIDTH);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
onScroll={Animated.event(
|
||||
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
|
||||
)}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
<View style={styles.slideStyle}>
|
||||
<View style={{ flex: 1, justifyContent: 'center' }}>
|
||||
<ProfileAvatar
|
||||
{...this.props}
|
||||
formType={CONTACT_FORM}
|
||||
isEditMode
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.slideStyle}>
|
||||
<BizCard
|
||||
formType={CONTACT_FORM}
|
||||
isEditMode
|
||||
isScanBizCardScreen={this.props.isScanBizCardScreen}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.dotsStyle}>
|
||||
{DOTS_SIZE.map((_, i) => {
|
||||
const opacity = position.interpolate({
|
||||
inputRange: [i - 1, i, i + 1],
|
||||
outputRange: [0.3, 1, 0.3],
|
||||
extrapolate: 'clamp'
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
key={i}
|
||||
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
</View>
|
||||
{this.renderDetailsSection()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the top section under the avatar
|
||||
**/
|
||||
renderDetailsSection() {
|
||||
return (
|
||||
<View>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.fullName_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.fullName}
|
||||
placeholder={strings('common.contactFields.fullName_placeholder')}
|
||||
onChangeText={value => this.splitNameField(value)}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.headline_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.headline}
|
||||
placeholder={strings('common.contactFields.headline_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'headline', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.titleAndCompany_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.titleAndCompany}
|
||||
placeholder={strings('common.contactFields.titleAndCompany_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'titleAndCompany', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.location_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.location}
|
||||
placeholder={strings('common.contactFields.location_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'location', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the primary phone section
|
||||
**/
|
||||
renderPrimaryPhoneSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_primary_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.primaryPhone}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'primaryPhone', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the secondary phone section
|
||||
**/
|
||||
renderSecondaryPhoneSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_secondary_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.secondaryPhone}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'secondaryPhone', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the work phone section
|
||||
**/
|
||||
renderWorkPhoneSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_work_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.workPhone}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'workPhone', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.extension_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.extension}
|
||||
placeholder={strings('common.contactFields.extension_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'extension', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the fax phone section
|
||||
**/
|
||||
renderFaxSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.fax_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.fax}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'fax', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the email section
|
||||
**/
|
||||
renderEmailSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.email_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.contactEmail}
|
||||
placeholder={strings('common.contactFields.email_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'contactEmail', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the website section
|
||||
**/
|
||||
renderWebsiteSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.website_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.website}
|
||||
placeholder={strings('common.contactFields.website_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'website', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the address section
|
||||
**/
|
||||
renderAddressSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.address_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.street}
|
||||
placeholder={strings('common.contactFields.address_placeholder_street')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'street', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.address2}
|
||||
placeholder={strings('common.contactFields.address_placeholder_address2')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'address2', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.city}
|
||||
placeholder={strings('common.contactFields.address_placeholder_city')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'city', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.region}
|
||||
placeholder={strings('common.contactFields.address_placeholder_region')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'region', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.postalCode}
|
||||
placeholder={strings('common.contactFields.address_placeholder_postalCode')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'postalCode', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.country}
|
||||
placeholder={strings('common.contactFields.address_placeholder_country')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'country', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
{this.renderAvatarBizCardSlider()}
|
||||
</View>
|
||||
|
||||
{this.renderPrimaryPhoneSection()}
|
||||
{this.renderSecondaryPhoneSection()}
|
||||
{this.renderWorkPhoneSection()}
|
||||
{this.renderFaxSection()}
|
||||
|
||||
{this.renderEmailSection()}
|
||||
{this.renderWebsiteSection()}
|
||||
|
||||
{this.renderAddressSection()}
|
||||
|
||||
<Spacer />
|
||||
<FormLabel labelText={strings('common.contactFields.notes_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.notes}
|
||||
placeholder={strings('common.contactFields.notes_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'notes', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
containerStyle={{ minHeight: 200 }}
|
||||
inputStyle={styles.textBoxStyles}
|
||||
/>
|
||||
<FormMessage error={this.props.error} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
slideStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
},
|
||||
dividerStyle: {
|
||||
backgroundColor: colors.middleGrey,
|
||||
marginTop: 10,
|
||||
marginBottom: 15
|
||||
},
|
||||
dotsStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
centeredInputContainerStyle: {
|
||||
borderBottomWidth: 0,
|
||||
marginLeft: 15,
|
||||
marginRight: 0
|
||||
},
|
||||
centeredInputTextStyle: {
|
||||
textAlign: 'center',
|
||||
color: colors.black
|
||||
},
|
||||
inputTextStyle: {
|
||||
width: '100%',
|
||||
color: colors.black
|
||||
},
|
||||
centeredTextBoxStyles: {
|
||||
textAlign: 'center',
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
color: colors.black
|
||||
},
|
||||
nameTextStyle: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
textBoxStyles: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
borderBottomWidth: Platform.OS === 'android' ? 1 : 0,
|
||||
borderColor: colors.middleGrey,
|
||||
color: colors.black
|
||||
},
|
||||
formInputStyle: {
|
||||
color: colors.black
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If you add any items here be sure to update them in the ContactCreateScreen,
|
||||
* ContactEditScreen, ContactImportScreen, ContactActions, and ContactFormReducer
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
contactBizCardImg,
|
||||
contactAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
titleAndCompany,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
workPhone,
|
||||
extension,
|
||||
fax,
|
||||
contactEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
notes,
|
||||
contactCreateDate,
|
||||
error
|
||||
} = state.contactForm;
|
||||
|
||||
return {
|
||||
contactBizCardImg,
|
||||
contactAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
titleAndCompany,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
workPhone,
|
||||
extension,
|
||||
fax,
|
||||
contactEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
notes,
|
||||
contactCreateDate,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
contactFormReset, contactUpdate
|
||||
})(ContactForm);
|
||||
@ -1,714 +0,0 @@
|
||||
/** Contains the base fields for Social Display **/
|
||||
import React, { Component } from 'react';
|
||||
import { Animated, Dimensions, Linking, ScrollView, Share, View } from 'react-native';
|
||||
import { Card, Divider, Icon } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { socialFormReset } from '../../actions';
|
||||
import ProfileAvatar from '../ProfileAvatar';
|
||||
import BizCard from '../BizCard';
|
||||
import { FormLabel, Keyword, Spacer } from '../common';
|
||||
import { SOCIAL_FORM } from '../FormTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
import privateKeys from '../../../private_keys.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const DOTS_SIZE = ['first', 'second'];
|
||||
const DEV_LINKING_BASE_URL = privateKeys.deepLinking.devBaseUrl;
|
||||
const TAGFER_BASE_URL = privateKeys.deepLinking.tagferBaseUrl;
|
||||
const scrollX = new Animated.Value(0);
|
||||
|
||||
class SocialForm extends Component {
|
||||
|
||||
/**
|
||||
* Component Will Unmount
|
||||
**/
|
||||
componentWillUnmount() {
|
||||
this.props.socialFormReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the normal hardware process for sharing something via social, sms, etc
|
||||
* uid - the uid for the profile to be shared
|
||||
**/
|
||||
shareLink(tagferId, uid) {
|
||||
Share.share(
|
||||
{
|
||||
message: `Add me on Tagfer! ${DEV_LINKING_BASE_URL}${tagferId}/${uid}`,
|
||||
title: 'Add me on Tagfer!',
|
||||
url: `${TAGFER_BASE_URL}${tagferId}/${uid}`
|
||||
},
|
||||
{
|
||||
tintColor: 0x056ecf,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.PostToWeibo',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.CopyToPasteboard',
|
||||
'com.apple.UIKit.activity.AssignToContact',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.AddToReadingList',
|
||||
'com.apple.UIKit.activity.PostToFlickr',
|
||||
'com.apple.UIKit.activity.PostToVimeo',
|
||||
'com.apple.UIKit.activity.PostToTencentWeibo',
|
||||
'com.apple.UIKit.activity.AirDrop',
|
||||
'com.apple.UIKit.activity.OpenInIBooks',
|
||||
'com.apple.UIKit.activity.MarkupAsPDF',
|
||||
'com.apple.reminders.RemindersEditorExtension', //Reminders
|
||||
'com.apple.mobilenotes.SharingExtension', // Notes
|
||||
'com.apple.mobileslideshow.StreamShareService', // iCloud Photo Sharing - This also does nothing :{
|
||||
|
||||
// Not supported
|
||||
'com.linkedin.LinkedIn.ShareExtension', //LinkedIn
|
||||
'pinterest.ShareExtension', //Pinterest
|
||||
'com.google.GooglePlus.ShareExtension', //Google +
|
||||
'com.tumblr.tumblr.Share-With-Tumblr', //Tumblr
|
||||
'wefwef.YammerShare', //Yammer
|
||||
'com.hootsuite.hootsuite.HootsuiteShareExt', //HootSuite
|
||||
'net.naan.TwitterFonPro.ShareExtension-Pro', //Echofon
|
||||
'net.whatsapp.WhatsApp.ShareExtension' //WhatsApp
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar and biz card slider
|
||||
**/
|
||||
renderAvatarBizCardSlider() {
|
||||
const position = Animated.divide(scrollX, SCREEN_WIDTH);
|
||||
|
||||
if (this.props.isAddMode) {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
{this.renderDetailsSection()}
|
||||
<Spacer />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
onScroll={Animated.event(
|
||||
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
|
||||
)}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
<View style={styles.slideStyle}>
|
||||
<ProfileAvatar
|
||||
{...this.props}
|
||||
formType={SOCIAL_FORM}
|
||||
isEditMode={this.props.isEditMode}
|
||||
/>
|
||||
{this.renderDetailsSection()}
|
||||
</View>
|
||||
|
||||
<View style={styles.slideStyle}>
|
||||
<BizCard
|
||||
formType={SOCIAL_FORM}
|
||||
isEditMode={this.props.isEditMode}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.dotsStyle}>
|
||||
{DOTS_SIZE.map((_, i) => {
|
||||
const opacity = position.interpolate({
|
||||
inputRange: [i - 1, i, i + 1],
|
||||
outputRange: [0.3, 1, 0.3],
|
||||
extrapolate: 'clamp'
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
key={i}
|
||||
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
</View>
|
||||
|
||||
{this.renderConnectionButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the top section under the avatar
|
||||
**/
|
||||
renderDetailsSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'column', alignItems: 'center', marginLeft: -10 }}>
|
||||
<FormLabel bold labelText={`@${this.props.navigation.state.params.tagferId}`} textStyle={{ color: colors.darkGreen, fontSize: 18 }} />
|
||||
<FormLabel bold labelText={this.props.fullName} textStyle={{ fontSize: 22 }} />
|
||||
<FormLabel bold labelText={this.props.headline} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.titleAndCompany} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.location} textStyle={{ fontSize: 15 }} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'column', alignItems: 'center', marginLeft: -10 }}>
|
||||
<FormLabel bold labelText={`@${this.props.navigation.state.params.otherTagferId}`} textStyle={{ color: colors.darkGreen, fontSize: 18 }} />
|
||||
<FormLabel bold labelText={this.props.fullName} textStyle={{ fontSize: 22 }} />
|
||||
<FormLabel bold labelText={this.props.headline} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.titleAndCompany} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.location} textStyle={{ fontSize: 15 }} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the various buttons for this connection
|
||||
**/
|
||||
renderConnectionButtons() {
|
||||
if (this.props.isAddMode) {
|
||||
return <Divider style={styles.dividerStyle} />;
|
||||
}
|
||||
const { otherUserId, myFriendId, otherFriendId } = this.props.navigation.state.params;
|
||||
// NORMAL MODE
|
||||
return (
|
||||
<View>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
|
||||
<View style={styles.iconViewContainerStyle}>
|
||||
<Icon
|
||||
name={'phone'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.darkGreen}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => Linking.openURL(`tel:+${this.props.primaryPhone}`)}
|
||||
/>
|
||||
<Icon
|
||||
name={'action-redo'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.buttonBlue}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.shareLink(this.props.tagferId, this.props.userId)}
|
||||
/>
|
||||
<Icon
|
||||
name={'speech'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.darkBlue}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.props.navigation.navigate('messageScreen', { otherUserId, myFriendId, otherFriendId })}
|
||||
/>
|
||||
<Icon
|
||||
name={'note'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.orange}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.props.navigation.navigate('socialNotesScreen', { friendId: myFriendId })}
|
||||
/>
|
||||
</View>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the About section
|
||||
**/
|
||||
renderAboutSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.about_labelText')} />
|
||||
<FormLabel labelText={this.props.about} />
|
||||
|
||||
<FormLabel bold labelText={strings('common.contactFields.howIHelp_labelText_social')} />
|
||||
<FormLabel labelText={this.props.howIHelp} />
|
||||
|
||||
<FormLabel bold labelText={strings('common.contactFields.whatINeed_labelText_social')} />
|
||||
<FormLabel labelText={this.props.whatINeed} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Experience section
|
||||
**/
|
||||
renderExperienceSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
let isAddedExperienceEmpty = true;
|
||||
for (let i = 0; i < this.props.education.length; i++) {
|
||||
if (!isAddedExperienceEmpty) {
|
||||
break;
|
||||
}
|
||||
for (const property in this.props.education[i]) {
|
||||
if (this.props.education[i][property].length > 0) {
|
||||
isAddedExperienceEmpty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAddedExperienceEmpty) {
|
||||
const experienceList = this.props.education.map((data, index) => {
|
||||
if (index > 0) {
|
||||
return (
|
||||
<View key={index}>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
<FormLabel labelText={data.jobTitle} />
|
||||
<FormLabel labelText={data.jobCompany} />
|
||||
<FormLabel labelText={data.jobDates} />
|
||||
<FormLabel labelText={data.jobLocation} />
|
||||
<FormLabel labelText={data.jobSummary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View key={index}>
|
||||
<FormLabel labelText={data.jobTitle} />
|
||||
<FormLabel labelText={data.jobCompany} />
|
||||
<FormLabel labelText={data.jobDates} />
|
||||
<FormLabel labelText={data.jobLocation} />
|
||||
<FormLabel labelText={data.jobSummary} />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.experience_labelText')} />
|
||||
{experienceList}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Education section
|
||||
**/
|
||||
renderEducationSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
let isAddedEducationEmpty = true;
|
||||
for (let i = 0; i < this.props.education.length; i++) {
|
||||
if (!isAddedEducationEmpty) {
|
||||
break;
|
||||
}
|
||||
for (const property in this.props.education[i]) {
|
||||
if (this.props.education[i][property].length > 0) {
|
||||
isAddedEducationEmpty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAddedEducationEmpty) {
|
||||
const educationList = this.props.education.map((data, index) => {
|
||||
if (index > 0) {
|
||||
return (
|
||||
<View key={index}>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
<FormLabel labelText={data.school} />
|
||||
<FormLabel labelText={data.degree} />
|
||||
<FormLabel labelText={data.fieldOfStudy} />
|
||||
<FormLabel labelText={data.schoolDates} />
|
||||
<FormLabel labelText={data.educationSummary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View key={index}>
|
||||
<FormLabel labelText={data.school} />
|
||||
<FormLabel labelText={data.degree} />
|
||||
<FormLabel labelText={data.fieldOfStudy} />
|
||||
<FormLabel labelText={data.schoolDates} />
|
||||
<FormLabel labelText={data.educationSummary} />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.education_labelText')} />
|
||||
{educationList}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the primary phone section
|
||||
**/
|
||||
renderPrimaryPhoneSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { primaryPhone } = this.props;
|
||||
if (!primaryPhone) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_primary_labelText')} />
|
||||
<FormLabel labelText={this.props.primaryPhone} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the secondary phone section
|
||||
**/
|
||||
renderSecondaryPhoneSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { secondaryPhone } = this.props;
|
||||
if (!secondaryPhone) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_secondary_labelText')} />
|
||||
<FormLabel labelText={this.props.secondaryPhone} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the work phone section
|
||||
**/
|
||||
renderWorkPhoneSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { workPhone, extension } = this.props;
|
||||
if (!workPhone && !extension) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_work_labelText')} />
|
||||
<FormLabel labelText={this.props.workPhone} />
|
||||
|
||||
<FormLabel bold labelText={strings('common.contactFields.extension_labelText')} />
|
||||
<FormLabel labelText={this.props.extension} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the fax phone section
|
||||
**/
|
||||
renderFaxSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { fax } = this.props;
|
||||
if (!fax) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.fax_labelText')} />
|
||||
<FormLabel labelText={this.props.fax} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the email section
|
||||
**/
|
||||
renderEmailSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { profileEmail } = this.props;
|
||||
if (!profileEmail) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.email_labelText')} />
|
||||
<FormLabel labelText={this.props.profileEmail} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the website section
|
||||
**/
|
||||
renderWebsiteSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { website } = this.props;
|
||||
if (!website) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.website_labelText')} />
|
||||
<FormLabel labelText={this.props.website} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the address section
|
||||
**/
|
||||
renderAddressSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { street, address2, city, region, postalCode, country } = this.props;
|
||||
if (!street && !address2 && !city && !region && !postalCode && !country) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.address_labelText')} />
|
||||
<FormLabel labelText={this.props.street} />
|
||||
<FormLabel labelText={this.props.address2} />
|
||||
<FormLabel labelText={this.props.city} />
|
||||
<FormLabel labelText={this.props.region} />
|
||||
<FormLabel labelText={this.props.postalCode} />
|
||||
<FormLabel labelText={this.props.country} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the keyword section
|
||||
**/
|
||||
renderKeywordSection() {
|
||||
if (!this.props.navigation.state.params.keywords || this.props.navigation.state.params.keywords.length === 0) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Card>
|
||||
<View style={styles.keywordViewStyle}>
|
||||
{this.renderKeywords()}
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders each keyword onto the screen
|
||||
**/
|
||||
renderKeywords() {
|
||||
if (!this.props.keywords || !this.props.navigation.state.params.keywords) {
|
||||
return;
|
||||
}
|
||||
let keywordArray = [];
|
||||
if (this.props.isAddMode) {
|
||||
keywordArray = this.props.navigation.state.params.keywords.map((data, index) => {
|
||||
return (
|
||||
<Keyword
|
||||
key={index}
|
||||
title={data}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
keywordArray = Object.values(this.props.keywords).map((data, index) => {
|
||||
return (
|
||||
<Keyword
|
||||
key={index}
|
||||
title={data}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
return keywordArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.whiteBackground}>
|
||||
{this.renderAvatarBizCardSlider()}
|
||||
</View>
|
||||
{this.renderKeywordSection()}
|
||||
|
||||
{this.renderAboutSection()}
|
||||
{this.renderExperienceSection()}
|
||||
{this.renderEducationSection()}
|
||||
|
||||
{this.renderPrimaryPhoneSection()}
|
||||
{this.renderSecondaryPhoneSection()}
|
||||
{this.renderWorkPhoneSection()}
|
||||
{this.renderFaxSection()}
|
||||
|
||||
{this.renderEmailSection()}
|
||||
{this.renderWebsiteSection()}
|
||||
|
||||
{this.renderAddressSection()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
slideStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
},
|
||||
whiteBackground: {
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
dotsStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
dividerStyle: {
|
||||
backgroundColor: colors.offWhite,
|
||||
marginTop: 10,
|
||||
marginBottom: 15
|
||||
},
|
||||
iconViewContainerStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
iconStyle: {
|
||||
marginLeft: 20,
|
||||
marginRight: 20
|
||||
},
|
||||
keywordViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If you add any items here be sure to update the lists in
|
||||
* SocialAddScreen, SocialEditScreen, SocialActions, and SocialReducer
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
socialBizCardImg,
|
||||
socialAvatarImg,
|
||||
profileName,
|
||||
titleAndCompany,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
profileEmail,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
userId,
|
||||
profileId,
|
||||
friendId,
|
||||
tagferId,
|
||||
loading,
|
||||
error,
|
||||
success
|
||||
} = state.socialContacts;
|
||||
|
||||
return {
|
||||
socialBizCardImg,
|
||||
socialAvatarImg,
|
||||
profileName,
|
||||
titleAndCompany,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
profileEmail,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
userId,
|
||||
profileId,
|
||||
friendId,
|
||||
tagferId,
|
||||
loading,
|
||||
error,
|
||||
success
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
socialFormReset
|
||||
})(SocialForm);
|
||||
@ -1,85 +0,0 @@
|
||||
/** Contains the base fields for Task reuse **/
|
||||
import React, { Component } from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import { FormInput } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormMessage, FormLabel } from '../common/';
|
||||
import { taskFormReset, taskUpdate } from '../../actions';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
class TaskForm extends Component {
|
||||
/**
|
||||
* Component Will Unmount
|
||||
**/
|
||||
componentWillUnmount() {
|
||||
this.props.taskFormReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<FormLabel required labelText={strings('forms.task.taskName_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.taskName}
|
||||
placeholder={strings('forms.task.taskName_placeholder')}
|
||||
onChangeText={value => this.props.taskUpdate({ prop: 'taskName', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<FormLabel required labelText={strings('forms.task.taskDescription_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.taskDescription}
|
||||
placeholder={strings('forms.task.taskDescription_placeholder')}
|
||||
onChangeText={value => this.props.taskUpdate({ prop: 'taskDescription', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
inputStyle={styles.textBoxStyles}
|
||||
/>
|
||||
<FormMessage error={this.props.error} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
textBoxStyles: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
borderBottomWidth: Platform.OS === 'android' ? 1 : 0,
|
||||
borderColor: colors.middleGrey,
|
||||
color: colors.black
|
||||
},
|
||||
inputTextStyle: {
|
||||
color: colors.black
|
||||
}
|
||||
};
|
||||
|
||||
// If you add any items here be sure to update the lists in
|
||||
// TaskEditScreen, TaskCreateScreen, TaskActions, and TaskFormReducer
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
taskName,
|
||||
taskDescription,
|
||||
taskCreateDate,
|
||||
error
|
||||
} = state.taskForm;
|
||||
|
||||
return {
|
||||
taskName,
|
||||
taskDescription,
|
||||
taskCreateDate,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
taskFormReset, taskUpdate
|
||||
})(TaskForm);
|
||||
@ -1,87 +0,0 @@
|
||||
/** Pure component for each Co-Host Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { CheckBox, ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class CoHostListItem extends React.PureComponent {
|
||||
/**
|
||||
* Creates the contact avatar
|
||||
**/
|
||||
renderAvatar({ item }) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const { isChecked, item, onPress } = this.props;
|
||||
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName || ''}</Text>
|
||||
<Text>{item.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (item.profileAvatarImg) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.key}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={{ name: 'account-circle', size: 50, color: colors.lightGrey }}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default CoHostListItem;
|
||||
@ -1,117 +0,0 @@
|
||||
/** Pure component for each Contact Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { Icon, ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class ContactItem extends React.PureComponent {
|
||||
/**
|
||||
* When a contact is selected it goes to the edit contact screen
|
||||
**/
|
||||
onContactSelect(rowData) {
|
||||
this.props.navigation.navigate('contactEdit', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a friend is selected it goes to the edit friend screen
|
||||
**/
|
||||
onFriendSelect(rowData) {
|
||||
this.props.navigation.navigate('socialEditScreen', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the contact avatar
|
||||
**/
|
||||
renderAvatar({ item }) {
|
||||
if (item.otherProfileId) {
|
||||
if (item.profileAvatarImg) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={50}
|
||||
color={colors.lightGrey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// Phone contact
|
||||
if (item.contactAvatarImg) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.contactAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={50}
|
||||
color={colors.lightGrey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
let title = '';
|
||||
let subTitle = '';
|
||||
|
||||
if (item.otherProfileId) {
|
||||
title = `@${item.tagferId}`;
|
||||
subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName || ''}</Text>
|
||||
<Text>{item.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={() => this.onFriendSelect(item)}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
title = `${item.fullName}`;
|
||||
subTitle = `${item.titleAndCompany || ''}`;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={() => this.onContactSelect(item)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default ContactItem;
|
||||
@ -1,212 +0,0 @@
|
||||
/** Screen for listing contacts **/
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { SectionList, Text, View } from 'react-native';
|
||||
import { SearchBar } from 'react-native-elements';
|
||||
import { contactsFetch, friendsFetch } from '../../actions';
|
||||
import ContactItem from './ContactItem';
|
||||
import { Spacer, Spinner } from '../../components/common';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
let sections = [];
|
||||
let contactsAndFriends = [];
|
||||
|
||||
class ContactList extends Component {
|
||||
|
||||
state = { searchTerm: '', loading: true };
|
||||
|
||||
/**
|
||||
* Component Did Mount
|
||||
**/
|
||||
componentDidMount() {
|
||||
this.props.friendsFetch();
|
||||
this.props.contactsFetch();
|
||||
this.mergeFriendsAndContacts(this.props.contacts, this.props.friends);
|
||||
|
||||
this.createContactSections(contactsAndFriends);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Receive Props
|
||||
* Merge contacts and friends if both exist, otherwise use
|
||||
* the one that exists
|
||||
**/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.contacts && nextProps.friends) {
|
||||
this.mergeFriendsAndContacts(nextProps.contacts, nextProps.friends);
|
||||
this.createContactSections(contactsAndFriends);
|
||||
} else if (nextProps.contacts) {
|
||||
nextProps.contacts.sort((a, b) => this.compare(a, b));
|
||||
contactsAndFriends = nextProps.contacts;
|
||||
this.createContactSections(nextProps.contacts);
|
||||
} else if (nextProps.friends) {
|
||||
nextProps.friends.sort((a, b) => this.compare(a, b));
|
||||
contactsAndFriends = nextProps.friends;
|
||||
this.createContactSections(nextProps.friends);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the friends and contacts for all of the tabs
|
||||
**/
|
||||
mergeFriendsAndContacts(currentContacts, currentFriends) {
|
||||
contactsAndFriends = currentContacts.concat(currentFriends);
|
||||
contactsAndFriends.sort((a, b) => this.compare(a, b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats all contacts into groups and creates headers for the SectionList
|
||||
**/
|
||||
createContactSections(contacts) {
|
||||
for (let i = 0; i < contacts.length; i++) {
|
||||
sections.push({ title: contacts[i].fullName[0], data: contacts[i] });
|
||||
}
|
||||
sections = _.groupBy(contacts, d => d.fullName[0].toUpperCase());
|
||||
|
||||
sections = _.reduce(sections, (acc, next, index) => {
|
||||
acc.push({
|
||||
title: index,
|
||||
data: next
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for sorting contacts based on name property
|
||||
**/
|
||||
compare(a, b) {
|
||||
if (a.fullName.toLowerCase() < b.fullName.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.fullName.toLowerCase() > b.fullName.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters search results based on searchBar input
|
||||
* Checks if the term is found in either name field, titleAndCompany, or tagferId
|
||||
* Then creates headers based on found contacts
|
||||
**/
|
||||
searchUpdated(term) {
|
||||
contactsAndFriends.sort((a, b) => this.compare(a, b));
|
||||
let filteredProps = contactsAndFriends;
|
||||
|
||||
filteredProps = filteredProps.filter(
|
||||
(contactContainer) => {
|
||||
if (contactContainer.fullName.toLowerCase().indexOf(term) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (contactContainer.tagferId) {
|
||||
if (contactContainer.tagferId.toLowerCase().indexOf(term) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (contactContainer.titleAndCompany) {
|
||||
if (contactContainer.titleAndCompany.toLowerCase().indexOf(term) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
this.createContactSections(filteredProps);
|
||||
this.setState({ searchTerm: term });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique key for each contact
|
||||
**/
|
||||
keyExtractor = (item) => {
|
||||
return `${item.uid}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Section Headers that divide contact groups
|
||||
**/
|
||||
renderSectionHeader = ({ section }) => {
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{section.title}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders each row of the list
|
||||
**/
|
||||
renderItem = ({ item }) => {
|
||||
return <ContactItem {...this.props} item={item} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<Spinner size="large" />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<SearchBar
|
||||
onChangeText={(term) => this.searchUpdated(term.toLowerCase())}
|
||||
containerStyle={{ backgroundColor: colors.white }}
|
||||
inputStyle={{ backgroundColor: colors.white }}
|
||||
placeholder={strings('common.search.searchPlaceholder_contacts')}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
/>
|
||||
<SectionList
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
renderItem={this.renderItem}
|
||||
sections={sections}
|
||||
/>
|
||||
<Spacer space={45} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
sectionContainer: {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.lightGrey,
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
sectionTitle: {
|
||||
color: colors.black,
|
||||
fontSize: 14,
|
||||
marginBottom: 8,
|
||||
marginLeft: 16,
|
||||
marginRight: 16,
|
||||
marginTop: 24,
|
||||
opacity: 0.8
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
// converts contact object of objects into contact array of objects
|
||||
const contacts = _.map(state.contacts, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
//converts friend object of objects into friend array of objects
|
||||
const friends = _.map(state.friends, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
return { contacts, friends };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
contactsFetch, friendsFetch
|
||||
})(ContactList);
|
||||
@ -1,43 +0,0 @@
|
||||
/** Pure component for each Conversation Item **/
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class ConversationItem extends React.PureComponent {
|
||||
/**
|
||||
* Action to take when a result is selected
|
||||
**/
|
||||
onResultSelect(rowData) {
|
||||
this.props.navigation.navigate('messageScreen', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName}</Text>
|
||||
<Text>{item.titleAndCompany || '' }</Text>
|
||||
<Text>{new Date(item.lastMessageSentTime).toLocaleDateString()}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={{ name: 'account-circle', size: 40, color: colors.lightGrey }}
|
||||
onPress={() => this.onResultSelect(item)}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConversationItem;
|
||||
@ -1,203 +0,0 @@
|
||||
/** Screen for listing conversations **/
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { FlatList, Image, Text, View } from 'react-native';
|
||||
import { Button } from 'react-native-elements';
|
||||
import { conversationListFetch } from '../../actions';
|
||||
import ConversationItem from '../../components/lists/ConversationItem';
|
||||
import Footer from '../../components/common/Footer';
|
||||
import { Spacer, Spinner } from '../../components/common';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const LOGO = require('../../../assets/tagfer_brand_and_logo_512.png');
|
||||
|
||||
let conversationResults = [];
|
||||
|
||||
class ConversationList extends Component {
|
||||
static navigationOptions = ({ navigation }) => {
|
||||
return {
|
||||
title: strings('message.conversations_title'),
|
||||
headerLeft: (
|
||||
<Button
|
||||
icon={{
|
||||
name: 'settings',
|
||||
type: 'simple-line-icon',
|
||||
color: colors.middleGrey,
|
||||
size: 30
|
||||
}}
|
||||
onPress={() => navigation.navigate('DrawerOpen')}
|
||||
backgroundColor={colors.transparent}
|
||||
/>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
state = { loading: true, noItems: false };
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
this.props.conversationListFetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Did Mount
|
||||
**/
|
||||
componentDidMount() {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Receive Props
|
||||
**/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.conversations) {
|
||||
this.populateConversationResults(nextProps);
|
||||
}
|
||||
if (!_.isEqual(this.props.conversations, nextProps.conversations) || this.props.success) {
|
||||
this.setState({ loading: false });
|
||||
if (nextProps.conversations.length === 0) {
|
||||
this.setState({ noItems: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats conversationResults for the FlatList
|
||||
**/
|
||||
populateConversationResults(nextProps) {
|
||||
nextProps.conversations.sort((a, b) => this.compare(a, b));
|
||||
conversationResults = nextProps.conversations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for sorting conversationResults based on fullName property
|
||||
**/
|
||||
compare(a, b) {
|
||||
if (a.fullName.toLowerCase() < b.fullName.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.fullName.toLowerCase() > b.fullName.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique key for each contact
|
||||
**/
|
||||
keyExtractor = (item) => {
|
||||
return `${item.uid}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders each row of the list
|
||||
**/
|
||||
renderItem = ({ item }) => {
|
||||
return <ConversationItem {...this.props} item={item} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<Spinner size="large" />
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.noItems) {
|
||||
return (
|
||||
<View style={styles.noItemsViewStyle}>
|
||||
<Spacer />
|
||||
<Image
|
||||
source={LOGO}
|
||||
style={styles.logoStyle}
|
||||
/>
|
||||
<Spacer space={45} />
|
||||
|
||||
<Text style={styles.noItemsTextStyle}>
|
||||
{strings('message.noItems')}
|
||||
</Text>
|
||||
<View />
|
||||
<View style={styles.mainFooterStyle}>
|
||||
<Footer
|
||||
{...this.props}
|
||||
index={3}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
||||
<FlatList
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.renderItem}
|
||||
data={conversationResults}
|
||||
/>
|
||||
<View style={styles.mainFooterStyle}>
|
||||
<Footer
|
||||
{...this.props}
|
||||
index={3}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
noItemsViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
logoStyle: {
|
||||
height: 200,
|
||||
width: 200,
|
||||
marginTop: 15,
|
||||
marginBottom: 15
|
||||
},
|
||||
noItemsTextStyle: {
|
||||
fontSize: 18,
|
||||
marginHorizontal: 15
|
||||
},
|
||||
mainFooterStyle: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 60
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Map State To Props
|
||||
**/
|
||||
const mapStateToProps = state => {
|
||||
// converts conversation objects into conversation arrays
|
||||
const conversations = _.map(state.conversations, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
// Required for the avatar to load in the footer
|
||||
const {
|
||||
profileAvatarImg
|
||||
} = state.profileForm;
|
||||
|
||||
const { success } = state.conversationStatus;
|
||||
|
||||
return { conversations, profileAvatarImg, success };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
conversationListFetch
|
||||
})(ConversationList);
|
||||
@ -1,78 +0,0 @@
|
||||
/** Pure component for each Event Details Attendee Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class EventDetailsAttendeeItem extends React.PureComponent {
|
||||
/**
|
||||
* Creates the contact avatar
|
||||
**/
|
||||
renderAvatar({ profile }) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: profile.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
const { profile } = item;
|
||||
|
||||
const title = `@${profile.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{profile.fullName || ''}</Text>
|
||||
<Text>{profile.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (item.profileAvatarImg) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={this.renderAvatar({ profile })}
|
||||
rightIcon={
|
||||
<View />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={{ name: 'account-circle', size: 50, color: colors.lightGrey }}
|
||||
rightIcon={
|
||||
<View />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default EventDetailsAttendeeItem;
|
||||
@ -1,276 +0,0 @@
|
||||
/** Pure component for each Event Item **/
|
||||
import firebase from 'firebase';
|
||||
import React from 'react';
|
||||
import { Dimensions, Image, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Card, Icon } from 'react-native-elements';
|
||||
import { GOING, NOT_GOING } from '../../components/AttendeeStatusTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const TEMP_IMAGE = require('../../../assets/tagfer_brand_and_logo_512.png');
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
|
||||
let attendeesResult = {};
|
||||
let attendees = [];
|
||||
|
||||
class EventItem extends React.PureComponent {
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
async componentWillMount() {
|
||||
attendeesResult = await firebase.database().ref(`/eventAttendees/${this.props.item.uid}`).once('value');
|
||||
attendeesResult = JSON.parse(JSON.stringify(attendeesResult));
|
||||
|
||||
if (attendeesResult === null || (Object.keys(attendeesResult).length === 0 && attendeesResult.constructor === Object)) {
|
||||
return;
|
||||
}
|
||||
// If there are attendees we need to force a rerender since it doesn't happen on a pure component
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the main image for an event
|
||||
**/
|
||||
renderEventImage(item) {
|
||||
if (item.eventMainImg) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.eventMainImg }}
|
||||
style={styles.eventImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Image
|
||||
source={TEMP_IMAGE}
|
||||
style={styles.eventImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders attendees avatars on each event
|
||||
**/
|
||||
renderEventAttendees() {
|
||||
if (attendeesResult === null || (Object.keys(attendeesResult).length === 0 && attendeesResult.constructor === Object)) {
|
||||
return <Text>0 </Text>;
|
||||
}
|
||||
|
||||
attendees = [];
|
||||
|
||||
for (const property in attendeesResult) {
|
||||
if (attendeesResult[property].status === GOING) {
|
||||
attendees.push(attendeesResult[property]);
|
||||
}
|
||||
}
|
||||
|
||||
if (attendees.length === 0) {
|
||||
return <Text>0 </Text>;
|
||||
}
|
||||
|
||||
return attendees.map((attendee) => {
|
||||
if (attendee.profile.profileAvatarImg) {
|
||||
return (
|
||||
<Image
|
||||
key={attendee.uid}
|
||||
source={{ uri: attendee.profile.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
key={attendee.uid}
|
||||
name='account-circle'
|
||||
size={35}
|
||||
color={colors.lightTeal}
|
||||
containerStyle={{ marginBottom: 5 }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the attendee status section
|
||||
**/
|
||||
renderAttendeeStatus(status, profileName) {
|
||||
if (status === GOING) {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Icon
|
||||
name={'ios-checkmark-circle'}
|
||||
type="ionicon"
|
||||
size={30}
|
||||
color={colors.green}
|
||||
containerStyle={styles.iconStyle}
|
||||
/>
|
||||
<Text style={{ fontSize: 15, fontWeight: 'bold', color: colors.green }}>{status}</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 15, fontWeight: 'bold', alignSelf: 'center', marginBottom: 5, color: colors.buttonBlue }}>{profileName}</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (status === NOT_GOING) {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Icon
|
||||
name={'ios-checkmark-circle'}
|
||||
type="ionicon"
|
||||
size={30}
|
||||
color={colors.red}
|
||||
containerStyle={styles.iconStyle}
|
||||
/>
|
||||
<Text style={{ fontSize: 15, fontWeight: 'bold', color: colors.red }}>{status}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Icon
|
||||
name={'ios-checkmark-circle'}
|
||||
type="ionicon"
|
||||
size={30}
|
||||
color={colors.orange}
|
||||
containerStyle={styles.iconStyle}
|
||||
/>
|
||||
<Text style={{ fontSize: 15, fontWeight: 'bold', color: colors.orange }}>{status}</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 15, fontWeight: 'bold', alignSelf: 'center', marginBottom: 5, color: colors.buttonBlue }}>{profileName}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const {
|
||||
eventName,
|
||||
hostProfile,
|
||||
endTime,
|
||||
eventLocation,
|
||||
eventTimeZone,
|
||||
startDate,
|
||||
startTime,
|
||||
uid
|
||||
} = item;
|
||||
|
||||
const formattedStartTime = `${startTime.substr(0, 4)}${startTime.substr(7, startTime.length)}`;
|
||||
const formattedEndTime = `${endTime.substr(0, 4)}${endTime.substr(7, endTime.length)}`;
|
||||
|
||||
|
||||
let status = NOT_GOING;
|
||||
let profileName = '';
|
||||
if (this.props.myAttendeeStatus.length > 0) {
|
||||
for (let i = 0; i < this.props.myAttendeeStatus.length; i++) {
|
||||
if (this.props.myAttendeeStatus[i].uid === item.uid) {
|
||||
status = this.props.myAttendeeStatus[i].status;
|
||||
profileName = this.props.myAttendeeStatus[i].profile.profileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card containerStyle={{ padding: 0, marginHorizontal: 5 }}>
|
||||
<TouchableOpacity
|
||||
style={styles.eventContainer}
|
||||
onPress={() => this.props.navigation.navigate('eventDetailsScreen', { item, myAttendeeStatus: this.props.myAttendeeStatus })}
|
||||
>
|
||||
{this.renderEventImage(item)}
|
||||
|
||||
<View style={{ width: SCREEN_WIDTH * 0.5 }}>
|
||||
<Text style={styles.eventTitleStyle}>{eventName}</Text>
|
||||
<Text style={styles.eventMiddleText}>{startDate}</Text>
|
||||
<Text style={[styles.eventMiddleText, { fontSize: 12 }]}>{formattedStartTime} - {formattedEndTime} {eventTimeZone}</Text>
|
||||
<Text style={styles.eventMiddleText}>{eventLocation}</Text>
|
||||
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
||||
{this.renderEventAttendees()}
|
||||
<Text style={styles.eventSmallText}>{strings('event.homeScreen.areGoing_label')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.eventActionButtonsContainer}>
|
||||
{this.renderAttendeeStatus(status, profileName)}
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.shareEventContainer}
|
||||
onPress={() => {
|
||||
this.props.navigation.navigate('shareEventQRCodeScreen', { eventName, eventLocation, hostTagferId: hostProfile.tagferId, startDate, startTime: formattedStartTime, endTime: formattedEndTime, uid });
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={'action-redo'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.buttonBlue}
|
||||
containerStyle={styles.iconStyle}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
eventContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
width: '100%'
|
||||
},
|
||||
eventImageStyle: {
|
||||
width: SCREEN_WIDTH / 3,
|
||||
height: '100%',
|
||||
marginRight: 5,
|
||||
resizeMode: 'contain'
|
||||
},
|
||||
eventTitleStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginVertical: 5
|
||||
},
|
||||
eventMiddleText: {
|
||||
fontSize: 15,
|
||||
marginBottom: 5
|
||||
},
|
||||
eventSmallText: {
|
||||
fontSize: 12
|
||||
},
|
||||
eventActionButtonsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
borderTopWidth: 1,
|
||||
borderColor: colors.lightGrey,
|
||||
},
|
||||
shareEventContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderColor: colors.lightGrey
|
||||
},
|
||||
iconStyle: {
|
||||
marginLeft: 20,
|
||||
marginRight: 5,
|
||||
},
|
||||
shareButtonStyle: {
|
||||
fontSize: 18
|
||||
},
|
||||
avatarImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
marginBottom: 5,
|
||||
marginRight: 5
|
||||
}
|
||||
};
|
||||
|
||||
export default EventItem;
|
||||
@ -1,79 +0,0 @@
|
||||
/** Pure component for each Exhibitor Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class ExhibitorListItem extends React.PureComponent {
|
||||
/**
|
||||
* Creates the contact avatar
|
||||
**/
|
||||
renderAvatar({ item }) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const { item, onPress } = this.props;
|
||||
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName || ''}</Text>
|
||||
<Text>{item.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (item.profileAvatarImg) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
onPress={onPress}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
rightIcon={
|
||||
<View />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
onPress={onPress}
|
||||
leftIcon={{ name: 'account-circle', size: 50, color: colors.lightGrey }}
|
||||
rightIcon={
|
||||
<View />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default ExhibitorListItem;
|
||||
@ -1,237 +0,0 @@
|
||||
/** Pure component for each My Event Item **/
|
||||
import firebase from 'firebase';
|
||||
import React from 'react';
|
||||
import { Dimensions, Image, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Card, Icon } from 'react-native-elements';
|
||||
import { GOING, INTERESTED } from '../../components/AttendeeStatusTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const TEMP_IMAGE = require('../../../assets/tagfer_brand_and_logo_512.png');
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
|
||||
let attendeesResult = {};
|
||||
let attendees = [];
|
||||
let interested = [];
|
||||
|
||||
class MyEventItem extends React.PureComponent {
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
async componentWillMount() {
|
||||
attendeesResult = await firebase.database().ref(`/eventAttendees/${this.props.item.uid}`).once('value');
|
||||
attendeesResult = JSON.parse(JSON.stringify(attendeesResult));
|
||||
|
||||
if (attendeesResult === null || (Object.keys(attendeesResult).length === 0 && attendeesResult.constructor === Object)) {
|
||||
return;
|
||||
}
|
||||
// If there are attendees we need to force a rerender since it doesn't happen on a pure component
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the main image for an event
|
||||
**/
|
||||
renderEventImage(item) {
|
||||
if (item.eventMainImg) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.eventMainImg }}
|
||||
style={styles.eventImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Image
|
||||
source={TEMP_IMAGE}
|
||||
style={styles.eventImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders attendees avatars on each event
|
||||
**/
|
||||
renderEventAttendees() {
|
||||
if (attendeesResult === null || (Object.keys(attendeesResult).length === 0 && attendeesResult.constructor === Object)) {
|
||||
return <Text>0 </Text>;
|
||||
}
|
||||
|
||||
attendees = [];
|
||||
interested = [];
|
||||
|
||||
for (const property in attendeesResult) {
|
||||
if (attendeesResult[property].status === GOING) {
|
||||
attendees.push(attendeesResult[property]);
|
||||
}
|
||||
if (attendeesResult[property].status === INTERESTED) {
|
||||
interested.push(attendeesResult[property]);
|
||||
}
|
||||
}
|
||||
|
||||
if (attendees.length === 0) {
|
||||
return <Text>0 </Text>;
|
||||
}
|
||||
|
||||
return attendees.map((attendee) => {
|
||||
if (attendee.profile.profileAvatarImg) {
|
||||
return (
|
||||
<Image
|
||||
key={attendee.uid}
|
||||
source={{ uri: attendee.profile.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
key={attendee.uid}
|
||||
name='account-circle'
|
||||
size={35}
|
||||
color={colors.lightTeal}
|
||||
containerStyle={{ marginBottom: 5 }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
|
||||
return (
|
||||
<Card containerStyle={{ padding: 0, marginHorizontal: 5 }}>
|
||||
<TouchableOpacity
|
||||
style={styles.eventContainer}
|
||||
onPress={() => this.props.navigation.navigate('eventEditScreen', item)}
|
||||
>
|
||||
{this.renderEventImage(item)}
|
||||
|
||||
<View style={{ width: SCREEN_WIDTH * 0.5 }}>
|
||||
<Text style={styles.eventTitleStyle}>{item.eventName}</Text>
|
||||
<Text style={styles.eventMiddleText}>{item.startDate} | {item.startTime} {item.endTime} {item.eventTimeZone}</Text>
|
||||
<Text style={styles.eventMiddleText}>{item.location}</Text>
|
||||
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
||||
{this.renderEventAttendees(item)}
|
||||
<Text style={styles.eventSmallText}>{strings('event.homeScreen.areGoing_label')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View>
|
||||
<Text style={styles.attendeeCountInfoTextStyle}>
|
||||
<Text style={styles.attendeesTextStyle}>{attendees.length} {strings('event.homeScreen.attendees_label')}</Text>
|
||||
<Text style={styles.interestedTextStyle}> | {interested.length} {strings('event.homeScreen.interested_label')}</Text>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.eventActionButtonsContainer}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<TouchableOpacity style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginVertical: 10 }}>
|
||||
<Text style={styles.shareButtonStyle}>{strings('event.homeScreen.options_button')}</Text>
|
||||
<Icon
|
||||
name={'chevron-down'}
|
||||
type="material-community"
|
||||
size={20}
|
||||
color={colors.buttonBlue}
|
||||
containerStyle={styles.iconStyle}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.shareEventContainer}>
|
||||
<Icon
|
||||
name={'action-redo'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.buttonBlue}
|
||||
containerStyle={styles.iconStyle}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View style={{ marginVertical: 5, borderTopWidth: 1, borderColor: colors.lightGrey }}>
|
||||
<Text style={styles.attendeeCountInfoTextStyle}>
|
||||
<Text style={styles.interestedTextStyle}>{item.confirmedMeetingsCount} {strings('event.homeScreen.confirmed_label')} | </Text>
|
||||
<Text style={styles.attendeesTextStyle}>{item.newRequestsCount} {strings('event.homeScreen.requests_label')}</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
eventContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
width: '100%'
|
||||
},
|
||||
eventImageStyle: {
|
||||
width: SCREEN_WIDTH / 3,
|
||||
height: '100%',
|
||||
marginRight: 5,
|
||||
resizeMode: 'contain'
|
||||
},
|
||||
eventTitleStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginVertical: 5
|
||||
},
|
||||
eventMiddleText: {
|
||||
fontSize: 15,
|
||||
marginBottom: 5
|
||||
},
|
||||
eventSmallText: {
|
||||
fontSize: 12
|
||||
},
|
||||
eventActionButtonsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
borderTopWidth: 1,
|
||||
borderColor: colors.lightGrey,
|
||||
},
|
||||
shareEventContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderColor: colors.lightGrey
|
||||
},
|
||||
iconStyle: {
|
||||
marginLeft: 20,
|
||||
marginRight: 5,
|
||||
},
|
||||
attendeeCountInfoTextStyle: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'center',
|
||||
marginVertical: 5
|
||||
},
|
||||
attendeesTextStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: colors.darkBlue
|
||||
},
|
||||
interestedTextStyle: {
|
||||
fontSize: 14,
|
||||
color: colors.darkBlue
|
||||
},
|
||||
shareButtonStyle: {
|
||||
fontSize: 18
|
||||
},
|
||||
avatarImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
marginBottom: 5,
|
||||
marginRight: 5
|
||||
}
|
||||
};
|
||||
|
||||
export default MyEventItem;
|
||||
@ -1,55 +0,0 @@
|
||||
/** Pure component for each Network Search Item **/
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class NetworkSearchItem extends React.PureComponent {
|
||||
/**
|
||||
* Action to take when a result is selected
|
||||
**/
|
||||
onResultSelect(rowData) {
|
||||
this.props.navigation.navigate('socialAddScreen', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName || ''}</Text>
|
||||
<Text>{item.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (item.profileAvatarImg) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
avatar={{ uri: item.profileAvatarImg }}
|
||||
onPress={() => this.onResultSelect(item)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={{ name: 'account-circle', size: 40, color: colors.lightGrey }}
|
||||
onPress={() => this.onResultSelect(item)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NetworkSearchItem;
|
||||
@ -1,40 +0,0 @@
|
||||
/** Pure component for each Note Item **/
|
||||
import React from 'react';
|
||||
import { ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class NoteItem extends React.PureComponent {
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const title = new Date(item.uid).toLocaleDateString();
|
||||
const subTitle = item.note;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={styles.listItemTitleStyle}
|
||||
subtitleStyle={styles.listItemSubTitleStyle}
|
||||
subtitleNumberOfLines={0}
|
||||
hideChevron
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
listItemTitleStyle: {
|
||||
color: colors.middleGrey,
|
||||
fontSize: 13
|
||||
},
|
||||
listItemSubTitleStyle: {
|
||||
color: colors.black,
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
};
|
||||
|
||||
export default NoteItem;
|
||||
@ -1,34 +0,0 @@
|
||||
/** Pure component for each Notification Item **/
|
||||
import React from 'react';
|
||||
import { ListItem } from 'react-native-elements';
|
||||
import { REQUEST_SENT } from '../NotificationListTypes';
|
||||
|
||||
class NotificationItem extends React.PureComponent {
|
||||
/**
|
||||
* When a notification is selected it goes to the notification display screen
|
||||
**/
|
||||
onNotificationSelect(rowData) {
|
||||
this.props.navigation.navigate('notificationStatusScreen', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
// Other options are REFERRAL_RECEIVED, REQUEST_ACCEPTED, and REQUEST_RECEIVED
|
||||
const title = item.notificationType === !REQUEST_SENT ? `@${item.myTagferId}` : `@${item.otherTagferId}`;
|
||||
const subTitle = `${new Date(item.socialAddDate).toLocaleDateString()}`;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
onPress={() => this.onNotificationSelect(item)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NotificationItem;
|
||||
@ -1,246 +0,0 @@
|
||||
/** Screen for listing notifications **/
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dimensions, Image, SectionList, Text, View } from 'react-native';
|
||||
import NotificationItem from '../../components/lists/NotificationItem';
|
||||
import { Spacer, Spinner } from '../../components/common';
|
||||
import {
|
||||
fetchProfileInfoByProfileId,
|
||||
profileListReset,
|
||||
notificationsFetch
|
||||
} from '../../actions';
|
||||
import {
|
||||
REFERRAL_RECEIVED,
|
||||
REQUEST_ACCEPTED,
|
||||
REQUEST_SENT,
|
||||
REQUEST_RECEIVED
|
||||
} from '../NotificationListTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const LOGO = require('../../../assets/tagfer_brand_and_logo_512.png');
|
||||
|
||||
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
||||
|
||||
let sections = [];
|
||||
|
||||
class NotificationList extends Component {
|
||||
state = { loading: true, noItems: false };
|
||||
|
||||
/**
|
||||
* Component Did Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
this.props.profileListReset();
|
||||
this.props.notificationsFetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Receive Props
|
||||
**/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.notifications && nextProps.notifications.length > 0 && nextProps.profiles.length === 0) {
|
||||
const userIds = [];
|
||||
const profileIds = [];
|
||||
for (let i = 0; i < nextProps.notifications.length; i++) {
|
||||
userIds.push(nextProps.notifications[i].otherUserId);
|
||||
profileIds.push(nextProps.notifications[i].otherProfileId);
|
||||
}
|
||||
|
||||
if (userIds.length > 0) {
|
||||
this.props.fetchProfileInfoByProfileId(userIds, profileIds);
|
||||
}
|
||||
}
|
||||
if (nextProps.profiles) {
|
||||
this.createNotificationSections(nextProps.notifications, nextProps.profiles);
|
||||
}
|
||||
if (!_.isEqual(this.props.notifications, nextProps.notifications) || this.props.success) {
|
||||
this.setState({ loading: false });
|
||||
if (nextProps.notifications.length === 0) {
|
||||
this.setState({ noItems: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats all notifications into groups and creates headers for the SectionList
|
||||
**/
|
||||
createNotificationSections(notifications, profiles) {
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
for (let j = 0; j < profiles.length; j++) {
|
||||
if (notifications[i].otherProfileId === profiles[j].uid) {
|
||||
// combines the notification and profile data via profileId matching
|
||||
sections.push({
|
||||
title: notifications[i].notificationType,
|
||||
data: Object.assign(notifications[i], profiles[j])
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sections = _.groupBy(notifications, d => d.notificationType);
|
||||
sections = _.reduce(sections, (acc, next, index) => {
|
||||
acc.push({
|
||||
title: index,
|
||||
data: next
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique key for each notification
|
||||
**/
|
||||
keyExtractor = (item) => {
|
||||
return `${item.uid}_${item.myProfileId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Section Headers that divide notification groups
|
||||
**/
|
||||
renderSectionHeader = ({ section }) => {
|
||||
switch (section.title) {
|
||||
case REFERRAL_RECEIVED:
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{strings('notification.listHeader_referrals')}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case REQUEST_ACCEPTED:
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{strings('notification.listHeader_acceptedRequests')}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case REQUEST_RECEIVED:
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{strings('notification.listHeader_receivedRequests')}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case REQUEST_SENT:
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{strings('notification.listHeader_sentRequests')}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.sectionTitle}>{section.title}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders each row of the list
|
||||
**/
|
||||
renderItem = ({ item }) => {
|
||||
return <NotificationItem {...this.props} item={item} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
||||
<Spinner size="large" />;
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.noItems) {
|
||||
return (
|
||||
<View style={styles.noItemsViewStyle}>
|
||||
<Spacer />
|
||||
<Image
|
||||
source={LOGO}
|
||||
style={styles.logoStyle}
|
||||
/>
|
||||
<Spacer space={45} />
|
||||
|
||||
<View>
|
||||
<Text style={styles.noItemsTextStyle}>
|
||||
{strings('notification.noItems')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.white, minHeight: SCREEN_HEIGHT }}>
|
||||
<SectionList
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
renderItem={this.renderItem}
|
||||
sections={sections}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
sectionContainer: {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.transparent,
|
||||
backgroundColor: colors.offWhite
|
||||
},
|
||||
sectionTitle: {
|
||||
color: colors.black,
|
||||
fontSize: 14,
|
||||
marginBottom: 8,
|
||||
marginLeft: 16,
|
||||
marginRight: 16,
|
||||
marginTop: 24,
|
||||
opacity: 0.8
|
||||
},
|
||||
noItemsViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.white,
|
||||
height: SCREEN_HEIGHT
|
||||
},
|
||||
logoStyle: {
|
||||
height: 200,
|
||||
width: 200,
|
||||
marginTop: 15,
|
||||
marginBottom: 15
|
||||
},
|
||||
noItemsTextStyle: {
|
||||
fontSize: 18,
|
||||
marginHorizontal: 15
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
// converts notification object of objects into notification array of objects
|
||||
const notifications = _.map(state.notifications, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
// converts profiles object of objects into profiles array of objects
|
||||
const profiles = _.map(state.profiles, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
const { success } = state.notificationStatus;
|
||||
|
||||
return { profiles, notifications, success };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
fetchProfileInfoByProfileId, profileListReset, notificationsFetch,
|
||||
})(NotificationList);
|
||||
@ -1,132 +0,0 @@
|
||||
/** Screen for listing profiles **/
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ListView } from 'react-native';
|
||||
import { List, ListItem } from 'react-native-elements';
|
||||
import { profileFetch, propertiesFetch, socialSendFriendRequest } from '../../actions';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
class ProfileList extends Component {
|
||||
static navigationOptions = () => {
|
||||
return {
|
||||
title: strings('profile.list_title')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
this.props.profileFetch();
|
||||
this.props.propertiesFetch();
|
||||
this.createDataSource(this.props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Receive Props
|
||||
**/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.profiles) {
|
||||
this.createDataSource(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When selected from the ProfileListScreen navigates to the edit screen
|
||||
* When called from the SocialAddFriendScreen the user chooses an profile to share
|
||||
* profileId - the profileId of the requested user
|
||||
* userId - the userId of the requested user
|
||||
**/
|
||||
onProfileSelect(rowData) {
|
||||
if (this.props.isProfileSelectModal) {
|
||||
const { tagferId, otherUserId, otherProfileId } = this.props;
|
||||
const myProfileId = rowData.uid;
|
||||
const socialAddDate = new Date().toString();
|
||||
|
||||
this.props.socialSendFriendRequest({
|
||||
myProfileId,
|
||||
myTagferId: tagferId,
|
||||
otherUserId,
|
||||
otherTagferId: this.props.navigation.state.params.tagferId,
|
||||
otherProfileId,
|
||||
socialAddDate
|
||||
});
|
||||
this.props.closeModal();
|
||||
this.props.navigation.goBack();
|
||||
} else {
|
||||
this.props.navigation.navigate('profileEdit', rowData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a data source for each profile in the list
|
||||
**/
|
||||
createDataSource({ profiles }) {
|
||||
const ds = new ListView.DataSource({
|
||||
rowHasChanged: (r1, r2) => r1 !== r2
|
||||
});
|
||||
|
||||
this.dataSource = ds.cloneWithRows(profiles);
|
||||
}
|
||||
/**
|
||||
* Renders each row of the list
|
||||
**/
|
||||
renderRow(rowData) {
|
||||
const title = `${rowData.profileName}`;
|
||||
const subTitle = `${rowData.titleAndCompany}`;
|
||||
if (rowData.profileAvatarImg) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={rowData.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
avatar={{ uri: rowData.profileAvatarImg }}
|
||||
onPress={() => this.onProfileSelect(rowData)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={rowData.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={{ name: 'account-circle', size: 40, color: colors.lightGrey }}
|
||||
onPress={() => this.onProfileSelect(rowData)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<List>
|
||||
<ListView
|
||||
enableEmptySections
|
||||
dataSource={this.dataSource}
|
||||
renderRow={this.renderRow.bind(this)}
|
||||
/>
|
||||
</List>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
// converts Profile objects into profile arrays
|
||||
const profiles = _.map(state.profiles, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
const { tagferId } = state.properties;
|
||||
|
||||
return { profiles, tagferId };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
profileFetch, propertiesFetch, socialSendFriendRequest
|
||||
})(ProfileList);
|
||||
@ -1,219 +0,0 @@
|
||||
/** Screen for listing socialContacts **/
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { FlatList, View } from 'react-native';
|
||||
import { SearchBar } from 'react-native-elements';
|
||||
import {
|
||||
profileFetch,
|
||||
friendsFetch,
|
||||
notificationsFetch,
|
||||
socialListReset,
|
||||
socialSearch
|
||||
} from '../../actions';
|
||||
import NetworkSearchItem from '../../components/lists/NetworkSearchItem';
|
||||
import { Spinner } from '../../components/common';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
let searchResults = [];
|
||||
|
||||
class SocialList extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.onChangeTextDelayed = _.debounce(this.searchUpdated, 250);
|
||||
this.state = { searchTerm: '', loading: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Did Mount
|
||||
**/
|
||||
componentDidMount() {
|
||||
this.props.profileFetch();
|
||||
this.props.friendsFetch();
|
||||
this.props.notificationsFetch();
|
||||
this.props.socialListReset();
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Receive Props
|
||||
**/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.socialContacts) {
|
||||
this.populateSearchResults(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats searchResults for the FlatList
|
||||
**/
|
||||
populateSearchResults(nextProps) {
|
||||
if (nextProps) {
|
||||
nextProps.socialContacts.sort((a, b) => this.compare(a, b));
|
||||
searchResults = this.removeMyProfilesFromSearchResults(nextProps.socialContacts);
|
||||
searchResults = this.removeMyFriendsFromSearchResults(nextProps.socialContacts);
|
||||
searchResults = this.removeMyNotificationsFromSearchResults(nextProps.socialContacts);
|
||||
} else {
|
||||
this.props.socialContacts.sort((a, b) => this.compare(a, b));
|
||||
searchResults = this.removeMyProfilesFromSearchResults(this.props.socialContacts);
|
||||
searchResults = this.removeMyFriendsFromSearchResults(this.props.socialContacts);
|
||||
searchResults = this.removeMyNotificationsFromSearchResults(this.props.socialContacts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any of the user's profiles from displaying as options for them to add
|
||||
**/
|
||||
removeMyProfilesFromSearchResults(searchResultsContacts) {
|
||||
const profiles = this.props.profiles;
|
||||
const searchContacts = searchResultsContacts;
|
||||
|
||||
for (let i = 0; i < searchContacts.length; i++) {
|
||||
for (let j = 0; j < profiles.length; j++) {
|
||||
if (searchContacts[i].uid === profiles[j].uid) {
|
||||
searchContacts.splice(i, 1);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchContacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any of the user's current friends from displaying on search results
|
||||
**/
|
||||
removeMyFriendsFromSearchResults(searchResultsContacts) {
|
||||
const friends = this.props.friends;
|
||||
const searchContacts = searchResultsContacts;
|
||||
|
||||
for (let i = 0; i < searchContacts.length; i++) {
|
||||
for (let j = 0; j < friends.length; j++) {
|
||||
if (searchContacts[i].uid === friends[j].uid) {
|
||||
searchContacts.splice(i, 1);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchContacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any of the user's current notifications from displaying on search results
|
||||
**/
|
||||
removeMyNotificationsFromSearchResults(searchResultsContacts) {
|
||||
const notifications = this.props.notifications;
|
||||
const searchContacts = searchResultsContacts;
|
||||
|
||||
for (let i = 0; i < searchContacts.length; i++) {
|
||||
for (let j = 0; j < notifications.length; j++) {
|
||||
if (searchContacts[i].profileId === notifications[j].otherProfileId) {
|
||||
searchContacts.splice(i, 1);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchContacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for sorting searchResults based on fullName property
|
||||
**/
|
||||
compare(a, b) {
|
||||
if (a.fullName.toLowerCase() < b.fullName.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.fullName.toLowerCase() > b.fullName.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters search results based on searchBar input
|
||||
**/
|
||||
searchUpdated = (term) => {
|
||||
const trimmedTerm = term.trim();
|
||||
if (trimmedTerm.length <= 2) {
|
||||
this.props.socialListReset();
|
||||
} else {
|
||||
this.props.socialSearch(trimmedTerm);
|
||||
this.populateSearchResults();
|
||||
|
||||
this.setState({ searchTerm: trimmedTerm });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique key for each contact
|
||||
**/
|
||||
keyExtractor = (item) => {
|
||||
return `${item.uid}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders each row of the list
|
||||
**/
|
||||
renderItem = ({ item }) => {
|
||||
return <NetworkSearchItem {...this.props} item={item} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<Spinner size="large" />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<SearchBar
|
||||
onChangeText={this.onChangeTextDelayed}
|
||||
containerStyle={{ backgroundColor: colors.white }}
|
||||
inputStyle={{ backgroundColor: colors.white }}
|
||||
placeholder={strings('common.search.searchPlaceholder_network')}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
/>
|
||||
<FlatList
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.renderItem}
|
||||
data={searchResults}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
// converts Profile objects into profile arrays
|
||||
const profiles = _.map(state.profiles, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
//converts friend object of objects into friend array of objects
|
||||
const friends = _.map(state.friends, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
// converts notification object of objects into notification array of objects
|
||||
const notifications = _.map(state.notifications, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
// converts contact object of objects into contact array of objects
|
||||
const socialContacts = _.map(state.socialList, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
return { profiles, friends, notifications, socialContacts };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
profileFetch, friendsFetch, notificationsFetch, socialListReset, socialSearch
|
||||
})(SocialList);
|
||||
@ -1,121 +0,0 @@
|
||||
/** Pure component for each Speaker Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { CheckBox, ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class SpeakerListItem extends React.PureComponent {
|
||||
/**
|
||||
* Creates the contact avatar
|
||||
**/
|
||||
renderAvatar({ item }) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const { isChecked, item, onPress, hasCheckBox } = this.props;
|
||||
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName || ''}</Text>
|
||||
<Text>{item.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (item.profileAvatarImg) {
|
||||
if (hasCheckBox) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// Don't display the checkbox - for Event Details Screen
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<View />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasCheckBox) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.key}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={{ name: 'account-circle', size: 50, color: colors.lightGrey }}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// Don't display the checkbox - for Event Details Screen
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.key}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={{ name: 'account-circle', size: 50, color: colors.lightGrey }}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<View />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default SpeakerListItem;
|
||||
@ -1,80 +0,0 @@
|
||||
/** Pure component for each Wallet Request Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { Icon, ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class WalletRequestItem extends React.PureComponent {
|
||||
/**
|
||||
* Navigates to the request details screen
|
||||
**/
|
||||
onRequestItemSelect(rowData) {
|
||||
this.props.navigation.navigate('requestDetailsScreen', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the requesting users' avatar
|
||||
**/
|
||||
renderRequestAvatar({ item }) {
|
||||
if (item.socialBizCardImg) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.socialBizCardImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={75}
|
||||
color={colors.lightGrey}
|
||||
containerStyle={{ marginRight: 10 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName} | {item.transactionAmount} TT</Text>
|
||||
<Text
|
||||
style={{ fontWeight: 'bold', marginTop: 5 }}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.requestMessage}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={this.renderRequestAvatar({ item })}
|
||||
onPress={() => this.onRequestItemSelect(item)}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderWidth: 1,
|
||||
borderRadius: 30,
|
||||
marginLeft: 10,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default WalletRequestItem;
|
||||
@ -1,106 +0,0 @@
|
||||
/** Pure component for each Wallet Transaction Item **/
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Icon, ListItem } from 'react-native-elements';
|
||||
import { EVENT_TRANSACTION, EARNED_TRANSACTION, RECEIVED_TRANSACTION, REQUESTED_TRANSACTION, SENT_TRANSACTION } from '../../components/TransactionTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class WalletTransactionItem extends React.PureComponent {
|
||||
/**
|
||||
* Navigates to the transaction details screen
|
||||
**/
|
||||
onTransactionItemSelect(rowData) {
|
||||
this.props.navigation.navigate('transactionDetailsScreen', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the requesting users' avatar
|
||||
**/
|
||||
renderTransactionAvatar({ item }) {
|
||||
switch (item.transactionType) {
|
||||
case EVENT_TRANSACTION:
|
||||
return (
|
||||
<Icon
|
||||
name={'event'}
|
||||
size={25}
|
||||
color={colors.buttonBlue}
|
||||
containerStyle={{ marginRight: 5, marginLeft: -5 }}
|
||||
/>
|
||||
);
|
||||
case EARNED_TRANSACTION:
|
||||
return (
|
||||
<Icon
|
||||
name={'trophy'}
|
||||
type='entypo'
|
||||
size={25}
|
||||
color={colors.teal}
|
||||
containerStyle={{ marginRight: 5, marginLeft: -5 }}
|
||||
/>
|
||||
);
|
||||
case RECEIVED_TRANSACTION:
|
||||
return (
|
||||
<Icon
|
||||
name={'arrow-long-left'}
|
||||
type='entypo'
|
||||
size={25}
|
||||
color={colors.green}
|
||||
containerStyle={{ marginRight: 5, marginLeft: -5 }}
|
||||
/>
|
||||
);
|
||||
case REQUESTED_TRANSACTION:
|
||||
return (
|
||||
<Icon
|
||||
name={'hand'}
|
||||
type='entypo'
|
||||
size={25}
|
||||
color={colors.pink}
|
||||
containerStyle={{ marginRight: 5, marginLeft: -5 }}
|
||||
/>
|
||||
);
|
||||
case SENT_TRANSACTION:
|
||||
return (
|
||||
<Icon
|
||||
name={'arrow-long-right'}
|
||||
type='entypo'
|
||||
size={25}
|
||||
color={colors.orange}
|
||||
containerStyle={{ marginRight: 5, marginLeft: -5 }}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName} | {item.transactionAmount} TT</Text>
|
||||
<Text
|
||||
style={{ fontWeight: 'bold', marginTop: 5 }}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.transactionMessage}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={this.renderTransactionAvatar({ item })}
|
||||
onPress={() => this.onTransactionItemSelect(item)}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WalletTransactionItem;
|
||||
@ -4,7 +4,7 @@ import { ListItem, Avatar } from 'react-native-elements';
|
||||
|
||||
import colors from '../../config/colors.json';
|
||||
import { isEmpty } from '../../utils/aux';
|
||||
import TagferCheckbox from '../new/TagferCheckbox.js';
|
||||
import TagferCheckbox from './TagferCheckbox.js';
|
||||
|
||||
const ContactListItem = ({ contact, selected, onPress }) => {
|
||||
const { givenName, familyName, phoneNumbers } = contact;
|
||||
55
src/components/new/PasswordField.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { FormInput, CheckBox } from 'react-native-elements';
|
||||
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
import colors from '../../config/colors.json'
|
||||
|
||||
const PasswordField = ({ value, onChangeText, inputStyle, visible = false, onEyePress }) => {
|
||||
return (
|
||||
<View>
|
||||
{/* PASSWORD INPUT */}
|
||||
<FormInput
|
||||
secureTextEntry={!visible}
|
||||
value={value}
|
||||
onChangeText={onChangeText}
|
||||
placeholder={strings('userCreateFlow.signUp.minPassword')}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={inputStyle}
|
||||
/>
|
||||
|
||||
{/* PASSWORD EYE */}
|
||||
<CheckBox
|
||||
iconRight
|
||||
iconType='material-community'
|
||||
checkedIcon='eye-off'
|
||||
uncheckedIcon='eye'
|
||||
onIconPress={onEyePress}
|
||||
onPress={onEyePress}
|
||||
uncheckedColor={colors.grey}
|
||||
checkedColor={colors.grey}
|
||||
checked={visible}
|
||||
size={25}
|
||||
containerStyle={styles.passwordEye}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
passwordEye: {
|
||||
position: 'absolute',
|
||||
top: 5,
|
||||
right: 25,
|
||||
backgroundColor: colors.transparent,
|
||||
borderWidth: 0,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
marginRight: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default PasswordField;
|
||||
@ -3,7 +3,7 @@ import { Avatar } from 'react-native-elements';
|
||||
|
||||
import { isEmpty } from '../../utils/aux';
|
||||
|
||||
const TagferAvatar = ({ size, photoURL }) => {
|
||||
const TagferAvatar = ({ size, photoURL, style, color }) => {
|
||||
const uri = !isEmpty(photoURL) ? { uri: photoURL } : null;
|
||||
const icon = isEmpty(photoURL) ? { name: 'user', type: 'entypo' } : null;
|
||||
|
||||
@ -14,6 +14,8 @@ const TagferAvatar = ({ size, photoURL }) => {
|
||||
icon={icon}
|
||||
source={uri}
|
||||
activeOpacity={0.7}
|
||||
containerStyle={style}
|
||||
overlayContainerStyle={{ backgroundColor: color }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const baseurl = 'http://192.168.1.201:3000';
|
||||
const baseurl = 'https://us-central1-tagfer-inc.cloudfunctions.net/api';
|
||||
const endpoints = {
|
||||
login: {
|
||||
method: 'POST',
|
||||
@ -39,18 +39,6 @@ const endpoints = {
|
||||
signup: {
|
||||
method: 'PUT',
|
||||
url: `${baseurl}/auth/signup`
|
||||
},
|
||||
retrieveLinkedInInfo: (linkedInQS) => ({
|
||||
method: 'GET',
|
||||
url: `${baseurl}/auth/linkedin/get/profile${linkedInQS}`
|
||||
}),
|
||||
linkedInToken: {
|
||||
method: 'GET',
|
||||
url: `${baseurl}/auth/linkedin/token`
|
||||
},
|
||||
linkedInLogin: {
|
||||
method: 'GET',
|
||||
url: `${baseurl}/auth/linkedin/login`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import SignupScreenOne from '../screens/auth/SignupScreen1';
|
||||
import SignupScreenTwo from '../screens/auth/SignupScreen2';
|
||||
import SignupScreenThreeA from '../screens/auth/SignupScreen3a';
|
||||
import SignupScreenThreeB from '../screens/auth/SignupScreen3b';
|
||||
import SignupScreenFour from '../screens/auth/SignupScreen4/SignupScreen4';
|
||||
import SignupScreenFour from '../screens/auth/SignupScreen4';
|
||||
import SignupScreenFive from '../screens/auth/SignupScreen5';
|
||||
import SignupScreenSix from '../screens/auth/SignupScreen6';
|
||||
import TwitterWebView from '../screens/auth/TwitterWebView';
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
"education_educationSummary_placeholder": "Education Summary",
|
||||
"education_fieldOfStudy_placeholder": "Field Of Study",
|
||||
"education_school_placeholder": "College/University",
|
||||
"email_labelText": "Email Address",
|
||||
"email_or_tagferid_label": "EMAIL OR TAGFERID",
|
||||
"email_labelText": "EMAIL ADDRESS",
|
||||
"email_or_tagferid_label": "EMAIL OR TAGFER ID",
|
||||
"email_or_tagferid_placeholder": "username@tagfer.com",
|
||||
"email_placeholder": "Email Address",
|
||||
"experience_labelText": "Experience",
|
||||
@ -58,7 +58,7 @@
|
||||
"extension_labelText": "Extension",
|
||||
"extension_placeholder": "Extension",
|
||||
"fax_labelText": "Fax",
|
||||
"firstName_labelText": "First Name",
|
||||
"firstName_labelText": "FIRST NAME",
|
||||
"firstName_placeholder": "First Name",
|
||||
"FromTo_placeholder": "From - To",
|
||||
"fullName_labelText": "Name",
|
||||
@ -74,17 +74,17 @@
|
||||
"keywordsAdd_labelText2": "(Limit 8. Separate multiple keywords with a comma.)",
|
||||
"keywordsHeader_labelText": "Keywords",
|
||||
"keywords_placeholder": "Marketing",
|
||||
"lastName_labelText": "Last Name",
|
||||
"lastName_labelText": "LAST NAME",
|
||||
"lastName_placeholder": "Last Name",
|
||||
"linkedIn_import": "Import data from LinkedIn",
|
||||
"company_number_labelText": "Company Number",
|
||||
"company_number_placeholder": "0000000000",
|
||||
"company_title_labelText": "Title Profession",
|
||||
"company_title_placeholder": "E.g.: CEO, UX Designer",
|
||||
"company_name_labelText": "Company Name",
|
||||
"company_name_placeholder": "Your company name",
|
||||
"company_address_labelText": "Company Email Address",
|
||||
"company_address_placeholder": "Your company email address",
|
||||
"company_number_labelText": "COMPANY NUMBER",
|
||||
"company_number_placeholder": "(XXX) XXX-XXXX",
|
||||
"company_title_labelText": "TITLE PROFESSION",
|
||||
"company_title_placeholder": "CEO, UX Designer",
|
||||
"company_name_labelText": "COMPANY NAME",
|
||||
"company_name_placeholder": "Company name",
|
||||
"company_address_labelText": "COMPANY EMAIL ADDRESS",
|
||||
"company_address_placeholder": "example@email.com",
|
||||
"location_labelText": "Location",
|
||||
"location_placeholder": "Santa Clara, CA",
|
||||
"notes_labelText": "Notes",
|
||||
@ -100,7 +100,7 @@
|
||||
"referrer_labelText": "Referral Id",
|
||||
"referrer_placeholder": "Referral Id",
|
||||
"setPublic_labelText": "Make This Profile Public",
|
||||
"tagferId_labelText": "Tagfer ID",
|
||||
"tagferId_labelText": "TAGFER ID",
|
||||
"tagferId_placeholder": "Tagfer ID",
|
||||
"titleAndCompany_labelText": "Title and Company",
|
||||
"titleAndCompany_placeholder": "Founder and CEO of Acme Corp",
|
||||
@ -573,7 +573,7 @@
|
||||
},
|
||||
"signUp": {
|
||||
"minPassword": "6+ characters",
|
||||
"password": "Password",
|
||||
"password": "PASSWORD",
|
||||
"password2": "Confirm Password",
|
||||
"title": "Signup",
|
||||
"urgencyMessage": "Claim your Tagfer ID before it's taken."
|
||||
|
||||
135
src/realm/actions/AuthActions.js
Normal file
@ -0,0 +1,135 @@
|
||||
import Realm from 'realm';
|
||||
|
||||
import { UserSchema, ProfileSchema, InvitesSchema, ContactsSchema, SignupConfig, AuthConfig, SessionSchema } from '../schemas/AuthSchema';
|
||||
|
||||
// ############################ MAIN #############################
|
||||
|
||||
var authRealm;
|
||||
var signupRealm;
|
||||
|
||||
// Loads the signup state; a `then` should be chained at the component level to save the state
|
||||
export async function loadSignupState(screen) {
|
||||
const realm = await getRealm(screen);
|
||||
return getPreviousStateFor(screen, realm);
|
||||
}
|
||||
|
||||
// Saves signup state for screens 1-5, we do not save 6 as the data loaded is random and should change everytime.
|
||||
export async function saveSignupState(screen, state, isUpdate) {
|
||||
try {
|
||||
const realm = await getRealm(screen);
|
||||
realm.write(() => saveCurrentStateFor(screen, state, realm, isUpdate));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
export function isContactSelected(recordID) {
|
||||
return signupRealm.objectForPrimaryKey(ContactsSchema.name, recordID);
|
||||
}
|
||||
|
||||
export async function saveAuthState(sessionId) {
|
||||
try {
|
||||
const realm = await getRealm(0);
|
||||
realm.write(() => saveCurrentStateFor(0, { sessionId }, realm, !realm.empty));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAuthState() {
|
||||
try {
|
||||
const realm = await getRealm(0);
|
||||
return realm.objectForPrimaryKey(SessionSchema.name, primaryKey);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// ############################ HELPERS #############################
|
||||
async function getRealm(screen) {
|
||||
if (screen === 0) {
|
||||
if (!authRealm) {
|
||||
authRealm = await Realm.open(AuthConfig);
|
||||
}
|
||||
return authRealm;
|
||||
}
|
||||
|
||||
if (!signupRealm) {
|
||||
signupRealm = await Realm.open(SignupConfig);
|
||||
}
|
||||
return signupRealm;
|
||||
}
|
||||
|
||||
function getSchemaFor(screen) {
|
||||
if (screen === 0) {
|
||||
return SessionSchema;
|
||||
} else if (screen === 4) {
|
||||
return ProfileSchema;
|
||||
} else if (screen === 5) {
|
||||
return InvitesSchema;
|
||||
}
|
||||
|
||||
return UserSchema;
|
||||
}
|
||||
|
||||
|
||||
function getPreviousStateFor(screen, realm) {
|
||||
let isUpdate = true;
|
||||
let prevState = {};
|
||||
|
||||
if (screen === 0) {
|
||||
prevState = realm.objectForPrimaryKey(SessionSchema.name, primaryKey);
|
||||
isUpdate = !!prevState;
|
||||
} else if (screen === 1) {
|
||||
prevState = realm.objectForPrimaryKey(UserSchema.name, primaryKey);
|
||||
isUpdate = !!prevState;
|
||||
} else if (screen === 2) {
|
||||
const object = realm.objectForPrimaryKey(UserSchema.name, primaryKey);
|
||||
if (!!object && !!object.tagferId) {
|
||||
const { tagferId, twitterUsername } = object;
|
||||
prevState = { tagferId, twitterUsername };
|
||||
}
|
||||
} else if (screen === 3) {
|
||||
const object = realm.objectForPrimaryKey(UserSchema.name, primaryKey);
|
||||
if (!!object && !!object.phoneNumber) {
|
||||
const { phoneNumber, cca2, callingCode } = object;
|
||||
prevState = { phoneNumber, cca2, callingCode };
|
||||
}
|
||||
} else if (screen === 4) {
|
||||
prevState = realm.objectForPrimaryKey(ProfileSchema.name, primaryKey);
|
||||
isUpdate = !!prevState;
|
||||
} else if (screen === 5) {
|
||||
isUpdate = !!realm.objectForPrimaryKey(InvitesSchema.name, primaryKey);
|
||||
} else if (screen === 6) {
|
||||
const user = realm.objectForPrimaryKey(UserSchema.name, primaryKey);
|
||||
const profile = realm.objectForPrimaryKey(ProfileSchema.name, primaryKey);
|
||||
const invites = realm.objectForPrimaryKey(InvitesSchema.name, primaryKey);
|
||||
prevState = { user, profile, invites };
|
||||
}
|
||||
|
||||
return { isUpdate, prevState };
|
||||
}
|
||||
|
||||
function saveCurrentStateFor(screen, state, realm, isUpdate) {
|
||||
if (screen === -5) {
|
||||
return saveSelectedContacts(realm, state);
|
||||
}
|
||||
|
||||
const schema = getSchemaFor(screen);
|
||||
try {
|
||||
realm.create(schema.name, { key: primaryKey, ...state }, isUpdate);
|
||||
} catch(error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: `selectedContacts` is an iterator passed from Map.keys()
|
||||
function saveSelectedContacts(realm, selectedContacts) {
|
||||
realm.delete(realm.objects(ContactsSchema.name));
|
||||
|
||||
for(let recordID of selectedContacts) {
|
||||
realm.create(ContactsSchema.name, { recordID });
|
||||
}
|
||||
}
|
||||
|
||||
const primaryKey = 0;
|
||||
67
src/realm/schemas/AuthSchema.js
Normal file
@ -0,0 +1,67 @@
|
||||
export const UserSchema = {
|
||||
name: 'User',
|
||||
primaryKey: 'key',
|
||||
properties: {
|
||||
key: 'int',
|
||||
email: 'string',
|
||||
password: 'string',
|
||||
firstName: 'string',
|
||||
lastName: 'string',
|
||||
tagferId: 'string?',
|
||||
twitterUsername: 'bool?',
|
||||
phoneNumber: 'string?',
|
||||
cca2: 'string?',
|
||||
callingCode: 'string?',
|
||||
formattedPhoneNumber: 'string?'
|
||||
}
|
||||
};
|
||||
|
||||
export const ProfileSchema = {
|
||||
name: 'Profile',
|
||||
primaryKey: 'key',
|
||||
properties: {
|
||||
key: 'int',
|
||||
jobTitle: 'string',
|
||||
companyName: 'string',
|
||||
companyEmail: 'string',
|
||||
companyPhoneNumber: 'string',
|
||||
photoBytes: 'string?'
|
||||
}
|
||||
};
|
||||
|
||||
export const InvitesSchema = {
|
||||
name: 'Invites',
|
||||
primaryKey: 'key',
|
||||
properties: {
|
||||
key: 'int',
|
||||
tagferIds: 'string[]',
|
||||
phoneNumbers: 'string[]'
|
||||
}
|
||||
};
|
||||
|
||||
export const ContactsSchema = {
|
||||
name: 'Contacts',
|
||||
primaryKey: 'recordID',
|
||||
properties: {
|
||||
recordID: 'string'
|
||||
}
|
||||
};
|
||||
|
||||
export const SessionSchema = {
|
||||
name: 'Session',
|
||||
primaryKey: 'key',
|
||||
properties: {
|
||||
key: 'int',
|
||||
sessionId: 'string'
|
||||
}
|
||||
}
|
||||
|
||||
export const SignupConfig = {
|
||||
path: 'Signup.realm',
|
||||
schema: [UserSchema, ProfileSchema, InvitesSchema, ContactsSchema]
|
||||
};
|
||||
|
||||
export const AuthConfig = {
|
||||
path: 'Auth.realm',
|
||||
schema: [SessionSchema]
|
||||
};
|
||||
@ -81,8 +81,7 @@ export default class ForgotPasswordScreen extends React.Component {
|
||||
{/* TEXT INPUTS */}
|
||||
<View style={{ flex: 3 }}>
|
||||
<Spacer />
|
||||
<FormLabel
|
||||
required
|
||||
<FormLabel
|
||||
labelText={strings('common.contactFields.email_labelText').toUpperCase()}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
@ -106,7 +105,7 @@ export default class ForgotPasswordScreen extends React.Component {
|
||||
onPress={this.onRequestResetPress.bind(this)}
|
||||
title={strings('settings.forgotPasswordScreen.submitButton')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
disabled={!isEmail(this.state.email)}
|
||||
disabled={!isEmail(this.state.email) || this.state.loading}
|
||||
loading={this.state.loading}
|
||||
fontSize={20}
|
||||
/>
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Image, KeyboardAvoidingView, Platform, Dimensions, View, Text, Keyboard, TouchableWithoutFeedback } from 'react-native';
|
||||
import { Image, KeyboardAvoidingView, Platform, Dimensions, View, Text, Keyboard, ScrollView, Alert } from 'react-native';
|
||||
import { Button, FormInput } from 'react-native-elements';
|
||||
|
||||
import { FormLabel, Spacer, TextButton } from '../../components/common';
|
||||
import { strings } from '../../locales/i18n';
|
||||
import { fetchAuth } from '../../utils/fetch';
|
||||
import { showFlashErrorMessage } from '../../utils/errorHandler';
|
||||
import { saveAuthState } from '../../realm/actions/AuthActions';
|
||||
|
||||
import PasswordField from '../../components/new/PasswordField';
|
||||
import endpoints from '../../config/apiEndpoints';
|
||||
import errors from '../../config/errors';
|
||||
import colors from '../../config/colors.json';
|
||||
@ -26,30 +28,23 @@ export default class LoginScreen extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the login press performs the network request and parses the response
|
||||
* and displays the error if needed.
|
||||
*/
|
||||
onLoginPress() {
|
||||
async onLoginPress() {
|
||||
Keyboard.dismiss();
|
||||
this.setState({ loading: true });
|
||||
const body = this.getLoginRequestBody();
|
||||
const resp = await fetchAuth(endpoints.login, body)
|
||||
this.setState({ loading: false });
|
||||
|
||||
fetchAuth(endpoints.login, body).then(res => {
|
||||
this.setState({ loading: false });
|
||||
if (res.error) {
|
||||
this.clearInvalidInputField(res.error);
|
||||
showFlashErrorMessage(res.error);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setState({ loading: false, password: '' });
|
||||
showFlashErrorMessage(errors.APP_NETWORK_ERROR);
|
||||
});
|
||||
if (resp.error) {
|
||||
this.clearInvalidInputField(resp.error);
|
||||
showFlashErrorMessage(resp.error);
|
||||
} else {
|
||||
saveAuthState(resp.sessionId);
|
||||
Alert.alert('Signup Success 🎉', "Check back in a couple of weeks for more features.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the body of the request from the current state. It determines if the identifier is an email or a tagferId.
|
||||
*/
|
||||
// Creates the body of the request from the current state; determines if the identifier is an email or a tagferId.
|
||||
getLoginRequestBody() {
|
||||
const body = { password: this.state.password };
|
||||
if (this.state.id.indexOf('@') !== -1) {
|
||||
@ -72,13 +67,12 @@ export default class LoginScreen extends React.Component {
|
||||
* Validation for the disbaling the button, if the there is id or password is less than 6.
|
||||
*/
|
||||
isButtonDiabled() {
|
||||
return this.state.id.length < 1 || this.state.password.length < 6;
|
||||
return this.state.id.length < 1 || this.state.password.length < 6 || this.state.loading;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: colors.white }} keyboardShouldPersistTaps="handled" >
|
||||
<View style={{ flex: 1 }}>
|
||||
{/* LOGO */}
|
||||
<View style={styles.logoContainerStyle}>
|
||||
@ -92,7 +86,6 @@ export default class LoginScreen extends React.Component {
|
||||
<View style={{ flex: 3 }}>
|
||||
<Spacer />
|
||||
<FormLabel
|
||||
required
|
||||
labelText={strings('common.contactFields.email_or_tagferid_label')}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
@ -107,18 +100,15 @@ export default class LoginScreen extends React.Component {
|
||||
<Spacer />
|
||||
|
||||
<FormLabel
|
||||
required
|
||||
labelText={strings('userLoginScreen.password')}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
<FormInput
|
||||
secureTextEntry
|
||||
value={this.state.password}
|
||||
onChangeText={password => this.setState({ password })}
|
||||
placeholder={strings('userLoginScreen.password_placeholder')}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
<PasswordField
|
||||
value={this.state.password}
|
||||
onChangeText={password => this.setState({ password })}
|
||||
inputStyle={styles.inputTextStyle}
|
||||
visible={this.state.passwordVisible}
|
||||
onEyePress={() => this.setState({ passwordVisible: !this.state.passwordVisible })}
|
||||
/>
|
||||
<Spacer />
|
||||
{/* FORGOT PASSWORD OR SIGNUP */}
|
||||
@ -148,8 +138,8 @@ export default class LoginScreen extends React.Component {
|
||||
disabled={this.isButtonDiabled()}
|
||||
fontSize={20}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
@ -196,7 +186,6 @@ const styles = {
|
||||
backgroundColor: colors.buttonBlue
|
||||
},
|
||||
inputTextStyle: {
|
||||
marginLeft: 15,
|
||||
color: colors.black
|
||||
}
|
||||
};
|
||||
|
||||
@ -7,12 +7,15 @@ import { strings } from '../../locales/i18n';
|
||||
import { fetchAuth } from '../../utils/fetch';
|
||||
import { getErrorObject, showFlashErrorMessage } from '../../utils/errorHandler';
|
||||
import { isEmail, isEmpty } from '../../utils/aux';
|
||||
import { loadSignupState, saveSignupState } from '../../realm/actions/AuthActions';
|
||||
|
||||
import PasswordField from '../../components/new/PasswordField';
|
||||
import logo from '../../../assets/logo-round.png';
|
||||
import colors from '../../config/colors.json';
|
||||
import endpoints from '../../config/apiEndpoints';
|
||||
import errors from '../../config/errors';
|
||||
|
||||
|
||||
export default class SignupScreenOne extends React.Component {
|
||||
static navigationOptions = {
|
||||
header: null
|
||||
@ -26,43 +29,42 @@ export default class SignupScreenOne extends React.Component {
|
||||
email: '',
|
||||
password: '',
|
||||
loading: false,
|
||||
error: ''
|
||||
error: '',
|
||||
passwordVisible: false
|
||||
};
|
||||
|
||||
loadSignupState(1).then(result => {
|
||||
this.setState({ ...result.prevState });
|
||||
this.isUpdate = result.isUpdate;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handles the button press by sending the request, based on the response received either navigates the user
|
||||
* to SignupScreenTwo or displays an error message.
|
||||
*/
|
||||
onSaveButtonPress() {
|
||||
|
||||
// Saves the current information locally and handles error processing
|
||||
async onSaveButtonPress() {
|
||||
Keyboard.dismiss();
|
||||
this.setState({ loading: true });
|
||||
|
||||
const endpointWithParams = endpoints.emailExists(this.state.email);
|
||||
|
||||
fetchAuth(endpointWithParams).then(res => {
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (res.error) {
|
||||
showFlashErrorMessage(res.error);
|
||||
} else if (res.result === true) {
|
||||
this.input.shake();
|
||||
const errorObject = getErrorObject(errors.AUTH_EMAIL_ALREADY_EXISTS);
|
||||
this.setState({ error: errorObject.desc });
|
||||
} else {
|
||||
this.props.navigation.navigate('signup2');
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setState({ loading: false });
|
||||
showFlashErrorMessage(errors.APP_NETWORK_ERROR);
|
||||
});
|
||||
const resp = await fetchAuth(endpoints.emailExists(this.state.email));
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (resp.error) {
|
||||
showFlashErrorMessage(resp.error);
|
||||
} else if (resp.result === true) {
|
||||
this.input.shake();
|
||||
const errorObject = getErrorObject(errors.AUTH_EMAIL_ALREADY_EXISTS);
|
||||
this.setState({ error: errorObject.desc });
|
||||
} else {
|
||||
this.props.navigation.navigate('signup2');
|
||||
saveSignupState(1, this.state, this.isUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean function that says if the button is disabled based on if the fields are empty.
|
||||
*/
|
||||
isButtonDisabled() {
|
||||
const { firstName, lastName, email, password, error } = this.state;
|
||||
return isEmpty(firstName) || isEmpty(lastName) || !isEmail(email) || password.length < 6 || !isEmpty(error);
|
||||
const { firstName, lastName, email, password, error, loading } = this.state;
|
||||
return isEmpty(firstName) || isEmpty(lastName) || !isEmail(email) || password.length < 6 || !isEmpty(error) || loading;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
@ -75,15 +77,12 @@ export default class SignupScreenOne extends React.Component {
|
||||
</View>
|
||||
|
||||
{/* HEADER */}
|
||||
<View>
|
||||
<Text style={styles.headerStyle} >Signup</Text>
|
||||
</View>
|
||||
<Text style={styles.headerStyle} >Signup</Text>
|
||||
|
||||
{/* TEXT INPUTS */}
|
||||
<View style={{ flex: 3 }}>
|
||||
<Spacer />
|
||||
<FormLabel
|
||||
required
|
||||
labelText={strings('common.contactFields.firstName_labelText')}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
@ -98,7 +97,6 @@ export default class SignupScreenOne extends React.Component {
|
||||
<Spacer />
|
||||
|
||||
<FormLabel
|
||||
required
|
||||
labelText={strings('common.contactFields.lastName_labelText')}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
@ -113,7 +111,6 @@ export default class SignupScreenOne extends React.Component {
|
||||
<Spacer />
|
||||
|
||||
<FormLabel
|
||||
required
|
||||
labelText={strings('common.contactFields.email_labelText')}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
@ -131,19 +128,16 @@ export default class SignupScreenOne extends React.Component {
|
||||
</View>
|
||||
|
||||
<FormLabel
|
||||
required
|
||||
labelText={strings('userCreateFlow.signUp.password')}
|
||||
textStyle={styles.labelStyle}
|
||||
containerStyle={{ marginTop: 0 }}
|
||||
/>
|
||||
<FormInput
|
||||
secureTextEntry
|
||||
value={this.state.password}
|
||||
onChangeText={password => this.setState({ password })}
|
||||
placeholder={strings('userCreateFlow.signUp.minPassword')}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
<PasswordField
|
||||
value={this.state.password}
|
||||
onChangeText={password => this.setState({ password })}
|
||||
inputStyle={styles.inputTextStyle}
|
||||
visible={this.state.passwordVisible}
|
||||
onEyePress={() => this.setState({ passwordVisible: !this.state.passwordVisible })}
|
||||
/>
|
||||
|
||||
{/* TERMS AND CONDITIONS */}
|
||||
@ -157,17 +151,15 @@ export default class SignupScreenOne extends React.Component {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Button
|
||||
onPress={this.onSaveButtonPress.bind(this)}
|
||||
title={strings('common.buttons.saveAndContinue')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
disabled={this.isButtonDisabled()}
|
||||
loading={this.state.loading}
|
||||
fontSize={20}
|
||||
iconRight={styles.chevronIconStyle}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
onPress={this.onSaveButtonPress.bind(this)}
|
||||
title={strings('common.buttons.saveAndContinue')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
disabled={this.isButtonDisabled()}
|
||||
loading={this.state.loading}
|
||||
fontSize={20}
|
||||
iconRight={styles.chevronIconStyle}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
@ -192,7 +184,7 @@ const styles = {
|
||||
headerStyle: {
|
||||
color: colors.headerBlue,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: 20,
|
||||
marginLeft: 15,
|
||||
fontSize: 24
|
||||
},
|
||||
logoContainerStyle: {
|
||||
@ -207,7 +199,7 @@ const styles = {
|
||||
},
|
||||
buttonStyle: {
|
||||
width: SCREEN_WIDTH * 0.9,
|
||||
marginTop: 15,
|
||||
marginTop: 5,
|
||||
marginBottom: 30,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.buttonBlue
|
||||
@ -219,7 +211,6 @@ const styles = {
|
||||
style: { marginLeft: 0 }
|
||||
},
|
||||
inputTextStyle: {
|
||||
marginLeft: 15,
|
||||
color: colors.black
|
||||
},
|
||||
formValidationStyle: {
|
||||
@ -239,7 +230,6 @@ const styles = {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
labelStyle: {
|
||||
color: colors.labelBlue,
|
||||
textTransform: 'uppercase'
|
||||
color: colors.labelBlue
|
||||
}
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ import { FormLabel } from '../../components/common';
|
||||
import { strings } from '../../locales/i18n';
|
||||
import { isEmpty } from '../../utils/aux';
|
||||
import { getErrorObject, showFlashErrorMessage } from '../../utils/errorHandler';
|
||||
import { loadSignupState, saveSignupState } from '../../realm/actions/AuthActions';
|
||||
import { fetchAuth } from '../../utils/fetch';
|
||||
|
||||
import colors from '../../config/colors.json';
|
||||
@ -30,6 +31,10 @@ export default class SignupScreenTwo extends React.Component {
|
||||
twitterUsername: false
|
||||
};
|
||||
|
||||
loadSignupState(2).then(result => {
|
||||
this.isUpdate = result.isUpdate;
|
||||
this.setState({ ...result.prevState });
|
||||
});
|
||||
this.keyboardDidHide = this.keyboardDidHide.bind(this);
|
||||
this.onChangeText = this.onChangeText.bind(this);
|
||||
this.openTwitterWebView = this.openTwitterWebView.bind(this);
|
||||
@ -56,34 +61,24 @@ export default class SignupScreenTwo extends React.Component {
|
||||
this.setState({ tagferId, twitterDisabled: true, error });
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Tagfer API to check if the tagferId exists.
|
||||
* Toggles the loading state.
|
||||
* Displays an error if it does.
|
||||
* Displays a flash message if there are any other errors.
|
||||
*/
|
||||
onSaveButtonPress() {
|
||||
async onSaveButtonPress() {
|
||||
Keyboard.dismiss();
|
||||
this.setState({ loading: true });
|
||||
|
||||
const endpointWithParams = endpoints.tagferIdExists(this.state.tagferId.toLowerCase());
|
||||
const tagferId = this.state.tagferId;
|
||||
const resp = await fetchAuth(endpoints.tagferIdExists(tagferId));
|
||||
this.setState({ loading: false });
|
||||
|
||||
fetchAuth(endpointWithParams).then(res => {
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (res.error) {
|
||||
showFlashErrorMessage(res.error);
|
||||
} else if (res.result === true) {
|
||||
this.input.shake();
|
||||
const errorObject = getErrorObject(errors.AUTH_UID_ALREADY_EXISTS);
|
||||
this.setState({ error: errorObject.desc });
|
||||
} else {
|
||||
this.props.navigation.navigate('signup3a');
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setState({ loading: false });
|
||||
showFlashErrorMessage(errors.APP_NETWORK_ERROR);
|
||||
});
|
||||
if (resp.error) {
|
||||
showFlashErrorMessage(resp.error);
|
||||
} else if (resp.result === true) {
|
||||
this.input.shake();
|
||||
const errorObject = getErrorObject(errors.AUTH_UID_ALREADY_EXISTS);
|
||||
this.setState({ error: errorObject.desc });
|
||||
} else {
|
||||
this.props.navigation.navigate('signup3a');
|
||||
saveSignupState(2, this.state, this.isUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper to set the tagferId to the twitter username
|
||||
@ -94,12 +89,14 @@ export default class SignupScreenTwo extends React.Component {
|
||||
|
||||
// Network request to our API to get an OAuth token
|
||||
async getTwitterAuthURL() {
|
||||
this.setState({ loading: true });
|
||||
const twitter = { authenticate: 'https://api.twitter.com/oauth/authenticate' };
|
||||
const result = await fetchAuth(endpoints.twitterToken);
|
||||
|
||||
if (result.error) { throw result.error; }
|
||||
const resp = await fetchAuth(endpoints.twitterToken);
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (resp.error) { throw resp.error; }
|
||||
|
||||
return `${twitter.authenticate}?oauth_token=${result.token}`;
|
||||
return `${twitter.authenticate}?oauth_token=${resp.token}`;
|
||||
}
|
||||
|
||||
// Navigates to a twitter webview modal, when the username is fetched successfully it will execute onSuccess
|
||||
@ -122,7 +119,7 @@ export default class SignupScreenTwo extends React.Component {
|
||||
|
||||
// Button is diabled if there is no tagferId or there is an error
|
||||
isButtonDisabled() {
|
||||
return isEmpty(this.state.tagferId) || !isEmpty(this.state.error);
|
||||
return isEmpty(this.state.tagferId) || !isEmpty(this.state.error) || this.state.loading;
|
||||
}
|
||||
|
||||
// Disbaled if user is typing or we are during a request
|
||||
@ -169,7 +166,6 @@ export default class SignupScreenTwo extends React.Component {
|
||||
|
||||
{/* TEXT INPUT */}
|
||||
<FormLabel
|
||||
required
|
||||
labelText={strings('common.contactFields.tagferId_labelText')}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
@ -295,7 +291,6 @@ const styles = {
|
||||
color
|
||||
}),
|
||||
inputTextStyle: {
|
||||
marginLeft: 15,
|
||||
color: colors.black
|
||||
},
|
||||
examplesStyle: {
|
||||
@ -311,7 +306,6 @@ const styles = {
|
||||
fontSize: 17
|
||||
},
|
||||
labelStyle: {
|
||||
color: colors.labelBlue,
|
||||
textTransform: 'uppercase'
|
||||
color: colors.labelBlue
|
||||
}
|
||||
};
|
||||
|
||||
@ -9,8 +9,8 @@ import { showFlashErrorMessage } from '../../utils/errorHandler';
|
||||
import { fetchAuth } from '../../utils/fetch';
|
||||
|
||||
import colors from '../../config/colors.json';
|
||||
import errors from '../../config/errors';
|
||||
import endpoints from '../../config/apiEndpoints';
|
||||
import { loadSignupState, saveSignupState } from '../../realm/actions/AuthActions';
|
||||
|
||||
|
||||
export default class SignupScreenThreeA extends React.Component {
|
||||
@ -25,30 +25,34 @@ export default class SignupScreenThreeA extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
phoneNumber: '',
|
||||
country: {
|
||||
cca2: 'US',
|
||||
callingCode: '1'
|
||||
}
|
||||
cca2: 'US',
|
||||
callingCode: '1',
|
||||
loading: false
|
||||
};
|
||||
loadSignupState(3).then(result => {
|
||||
this.setState({ ...result.prevState });
|
||||
this.isUpdate = result.isUpdate;
|
||||
});
|
||||
}
|
||||
|
||||
// Reformat the masked number and send the request
|
||||
onSaveButtonPress() {
|
||||
async onSaveButtonPress() {
|
||||
this.setState({ loading: true });
|
||||
const phoneNumber = `+${this.state.country.callingCode}${this.input.getRawValue()}`;
|
||||
fetchAuth(endpoints.sendCode, { phoneNumber }).then(res => {
|
||||
if (res.error) {
|
||||
showFlashErrorMessage(res.error);
|
||||
} else {
|
||||
this.props.navigation.navigate('signup3b', { phoneNumber });
|
||||
}
|
||||
}).catch(() => showFlashErrorMessage(errors.APP_NETWORK_ERROR))
|
||||
.finally(() => this.setState({ loading: false }));
|
||||
const phoneNumber = `+${this.state.callingCode}${this.input.getRawValue()}`;
|
||||
const resp = await fetchAuth(endpoints.sendCode, { phoneNumber });
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (resp.error) {
|
||||
showFlashErrorMessage(resp.error);
|
||||
} else {
|
||||
this.props.navigation.navigate('signup3b', { phoneNumber });
|
||||
saveSignupState(3, { ...this.state, formattedPhoneNumber: phoneNumber }, this.isUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
// 14 chars include mask |(999) 999-9999|, its actually 10
|
||||
isDisabled() {
|
||||
return this.state.phoneNumber.length < 14;
|
||||
return this.state.phoneNumber.length < 14 || this.state.loading;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
@ -63,15 +67,14 @@ export default class SignupScreenThreeA extends React.Component {
|
||||
<CountryPicker
|
||||
closeable
|
||||
filterable
|
||||
onChange={(country) => this.setState({ country })}
|
||||
onClose={() => this.props.navigation.goBack()}
|
||||
cca2={this.state.country.cca2}
|
||||
onChange={(country) => this.setState({ ...country })}
|
||||
cca2={this.state.cca2}
|
||||
translation='eng'
|
||||
styles={{ itemCountryName: { borderBottomWidth: 0 } }}
|
||||
|
||||
/>
|
||||
{/* CALLING CODE */}
|
||||
<Text style={styles.callingCodeText}>+{this.state.country.callingCode}</Text>
|
||||
<Text style={styles.callingCodeText}>+{this.state.callingCode}</Text>
|
||||
|
||||
{/* PHONE NUMBER */}
|
||||
<TextInputMask
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Dimensions, KeyboardAvoidingView, Platform, ScrollView, Text, View } from 'react-native';
|
||||
import { Dimensions, KeyboardAvoidingView, Platform, ScrollView, Text, View, TouchableOpacity } from 'react-native';
|
||||
import { Button } from 'react-native-elements';
|
||||
import { TextInputMask } from 'react-native-masked-text';
|
||||
|
||||
@ -23,33 +23,45 @@ export default class SignupScreenThreeB extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
code: ''
|
||||
code: '',
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
|
||||
// Reformat the masked code and send the request
|
||||
onSaveButtonPress() {
|
||||
async onSaveButtonPress() {
|
||||
this.setState({ loading: true });
|
||||
const phoneNumber = this.props.navigation.state.params.phoneNumber;
|
||||
const code = this.input.getRawValue();
|
||||
fetchAuth(endpoints.verifyCode, { phoneNumber, code }).then(res => {
|
||||
if (res.error) {
|
||||
showFlashErrorMessage(res.error);
|
||||
} else if (res.result === false) {
|
||||
showFlashErrorMessage(errors.AUTH_VERIFICATION_CODE_MISMATCH);
|
||||
} else {
|
||||
this.props.navigation.navigate('signup4');
|
||||
}
|
||||
}).catch(() => showFlashErrorMessage(errors.APP_NETWORK_ERROR))
|
||||
.finally(() => this.setState({ loading: false }));
|
||||
const resp = await fetchAuth(endpoints.verifyCode, { phoneNumber, code });
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (resp.error) {
|
||||
showFlashErrorMessage(resp.error);
|
||||
} else if (resp.result === false) {
|
||||
showFlashErrorMessage(errors.AUTH_VERIFICATION_CODE_MISMATCH);
|
||||
} else {
|
||||
this.props.navigation.navigate('signup4');
|
||||
}
|
||||
}
|
||||
|
||||
async onResendPress() {
|
||||
this.setState({ loading: true });
|
||||
const phoneNumber = this.props.navigation.state.params.phoneNumber;
|
||||
const resp = await fetchAuth(endpoints.sendCode, { phoneNumber });
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (resp.error) {
|
||||
showFlashErrorMessage(resp.error);
|
||||
}
|
||||
}
|
||||
|
||||
isDisabled() {
|
||||
return this.state.code.length !== 7;
|
||||
return this.state.code.length !== 7 || this.state.loading;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const phoneNumber = this.props.navigation.state.params.phoneNumber;
|
||||
const phoneNumber = '';//this.props.navigation.state.params.phoneNumber;
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: colors.white }} keyboardShouldPersistTaps="handled">
|
||||
@ -77,8 +89,15 @@ export default class SignupScreenThreeB extends React.Component {
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Text style={styles.wrongNumberButtonStyle} onPress={() => this.props.navigation.goBack()}>Wrong Number?</Text>
|
||||
|
||||
{/* HELP BUTTONS */}
|
||||
<View style={{ marginTop: 25 }}>
|
||||
<TouchableOpacity onPress={() => this.props.navigation.goBack()}>
|
||||
<Text style={styles.textButtonStyle}>Wrong Number?</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => this.onResendPress()}>
|
||||
<Text style={styles.textButtonStyle}>Resend code?</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@ -151,11 +170,11 @@ const styles = {
|
||||
size: 30,
|
||||
style: { marginLeft: 0 }
|
||||
},
|
||||
wrongNumberButtonStyle: {
|
||||
textButtonStyle: {
|
||||
color: colors.darkBlue,
|
||||
fontSize: 15,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: 20,
|
||||
marginTop: 20
|
||||
marginBottom: 20
|
||||
}
|
||||
};
|
||||
|
||||
259
src/screens/auth/SignupScreen4.js
Normal file
@ -0,0 +1,259 @@
|
||||
/** Screen for creating default User Profile **/
|
||||
import React from 'react';
|
||||
import { Button, FormInput, Alert } from 'react-native-elements';
|
||||
import { KeyboardAvoidingView, Platform, ScrollView, Text, View, TouchableOpacity } from 'react-native';
|
||||
import OpenAppSettings from 'react-native-app-settings';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import { strings } from '../../locales/i18n';
|
||||
import { loadSignupState, saveSignupState } from '../../realm/actions/AuthActions';
|
||||
|
||||
import { FormLabel, Spacer, TextButton } from '../../components/common';
|
||||
import styles from './styles';
|
||||
import TagferAvatar from '../../components/new/TagferAvatar';
|
||||
import { isEmail } from '../../utils/aux';
|
||||
|
||||
export default class SignupScreenFour extends React.Component {
|
||||
static navigationOptions = {
|
||||
title: strings('userCreateFlow.defaultProfileCreate.title'),
|
||||
headerStyle: { borderBottomWidth: 0 },
|
||||
headerTintColor: '#0D497E',
|
||||
headerRight: <Text style={styles.navigationHeaderRightStyle}>4/6</Text>
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
jobTitle: '',
|
||||
companyName: '',
|
||||
companyEmail: '',
|
||||
companyPhoneNumber: '',
|
||||
photoBytes: '',
|
||||
loading: false
|
||||
};
|
||||
|
||||
loadSignupState(4).then(result => {
|
||||
this.isUpdate = result.isUpdate;
|
||||
this.setState({ ...result.prevState });
|
||||
});
|
||||
|
||||
this.renderAvatarPhoto = this.renderAvatarPhoto.bind(this);
|
||||
this.onUploadPhotoPress = this.onUploadPhotoPress.bind(this);
|
||||
this.onTakePhotoPress = this.onTakePhotoPress.bind(this);
|
||||
}
|
||||
|
||||
onSaveButtonPress() {
|
||||
this.props.navigation.navigate('signup5');
|
||||
saveSignupState(4, this.state, this.isUpdate);
|
||||
}
|
||||
|
||||
async onTakePhotoPress() {
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const response = await Permissions.request('camera');
|
||||
if (response !== 'authorized') {
|
||||
this.createSettingsAlert('Camera Access Deinied', 'To enable access, tap Settings and turn on Camera');
|
||||
this.setState({ loading: false });
|
||||
} else {
|
||||
ImagePicker.launchCamera({ mediaType: 'photo' }, result => this.setState({ loading: false, photoBytes: result.data }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async onUploadPhotoPress() {
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const response = await Permissions.request('photo');
|
||||
if (response !== 'authorized') {
|
||||
this.createSettingsAlert('Photos Access Deinied', 'To enable access, tap Settings and turn on Photos');
|
||||
this.setState({ loading: false });
|
||||
} else {
|
||||
ImagePicker.launchImageLibrary({ allowsEditing: true }, result => this.setState({ loading: false, photoBytes: result.data }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
createSettingsAlert(title, message) {
|
||||
Alert.alert(
|
||||
title,
|
||||
message,
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{ text: 'Settings', onPress: () => OpenAppSettings.open() }
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
}
|
||||
|
||||
isDisabled() {
|
||||
const { jobTitle, companyName, companyEmail, companyPhoneNumber, loading } = this.state;
|
||||
return !jobTitle || !companyName || !isEmail(companyEmail) || companyPhoneNumber.length < 10 || loading;
|
||||
}
|
||||
|
||||
renderLinkedInButtons() {
|
||||
return (
|
||||
<TextButton
|
||||
rightText={strings('common.contactFields.linkedIn_import')}
|
||||
rightTextStyle={{ fontSize: 18, fontWeight: 'normal', color: '#53ACF0' }}
|
||||
containerStyle={{ marginLeft: -2, marginTop: 15 }}
|
||||
onPress={this.openLinkedInWebView}
|
||||
disabled
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderAvatarPhoto() {
|
||||
let photoURL = '';
|
||||
if (this.state.photoBytes) {
|
||||
photoURL = `data:image/jpeg;base64,${this.state.photoBytes}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.avatarStyle}>
|
||||
<TagferAvatar size='xlarge' photoURL={photoURL} style={{ marginLeft: 25 }} color='#47C0E237' />
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderPhotoButtons() {
|
||||
return (
|
||||
<View style={styles.buttonContainerStyle}>
|
||||
<TouchableOpacity onPress={this.onUploadPhotoPress} disabled={this.props.loading}>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.uploadPhoto')}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={styles.photoButtonsOrTextStyle}>{strings('profile.or')}</Text>
|
||||
|
||||
<TouchableOpacity onPress={this.onTakePhotoPress} disabled={this.props.loading}>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.takePhoto')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderInputComponents(labelString, inputPlaceholderText, onChangeInput) {
|
||||
return (
|
||||
<View>
|
||||
<FormLabel
|
||||
labelText={labelString}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
<FormInput
|
||||
underlineColorAndroid={styles.colors.greyBlue}
|
||||
value={this.state[onChangeInput]}
|
||||
placeholder={inputPlaceholderText}
|
||||
onChangeText={value => this.setState({ [onChangeInput]: value })}
|
||||
inputStyle={styles.inputTextStyle}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<ScrollView contentContainerStyle={styles.parentContentScrollViewStyle} keyboardShouldPersistTaps="handled">
|
||||
<View style={{ flex: 1 }}>
|
||||
{/* INSTRUCTIONS */}
|
||||
<Text style={styles.instructions}>{strings('userCreateFlow.defaultProfileCreate.instructions')}</Text>
|
||||
<Spacer />
|
||||
<View style={{ flex: 1, flexDirection: 'column', justifyContent: 'center' }}>
|
||||
{this.renderAvatarPhoto()}
|
||||
<Spacer />
|
||||
{this.renderLinkedInButtons()}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_title_labelText'), strings('common.contactFields.company_title_placeholder'), 'jobTitle')}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_name_labelText'), strings('common.contactFields.company_name_placeholder'), 'companyName')}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_address_labelText'), strings('common.contactFields.company_address_placeholder'), 'companyEmail')}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_number_labelText'), strings('common.contactFields.company_number_placeholder'), 'companyPhoneNumber')}
|
||||
<Text style={{ marginLeft: 17, color: '#ABABAB' }}>{strings('userCreateFlow.defaultProfileCreate.no_verification_needed')}</Text>
|
||||
<Spacer />
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* SAVE AND CONTINUE BUTTON */ }
|
||||
<Button
|
||||
onPress={this.onSaveButtonPress.bind(this)}
|
||||
title={strings('common.buttons.saveAndContinue')}
|
||||
disabled={this.isDisabled()}
|
||||
loading={this.state.loading}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
fontSize={20}
|
||||
iconRight={styles.chevronIconStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (Platform.OS === 'android') {
|
||||
return this.renderContent();
|
||||
}
|
||||
|
||||
// IOS
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1 }}
|
||||
keyboardVerticalOffset={90}
|
||||
behavior="padding"
|
||||
>
|
||||
{this.renderContent()}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Moved here for debugging for now
|
||||
|
||||
// // Network request to our API to get an OAuth token
|
||||
// async getLinkedInAuthURL() {
|
||||
// console.log('getting linkedin auth info');
|
||||
// this.setState({ loading: true });
|
||||
// console.log('sending fetch request');
|
||||
// const result = await fetchAuth(endpoints.linkedInLogin);
|
||||
|
||||
// if (result.error) {
|
||||
// showFlashErrorMessage(result.error);
|
||||
// return null;
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// async getLinkedInData(linkedInAuthData) {
|
||||
// try {
|
||||
// const result = await fetchAuth(endpoints.retrieveLinkedInInfo(toQueryString(linkedInAuthData)));
|
||||
// if (result.error) {
|
||||
// showFlashErrorMessage(result.error);
|
||||
// } else {
|
||||
// //this call will be altered once we gefull linkedin partner api access
|
||||
// //this.setState({ profileInfo: result, loading: false });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// showFlashErrorMessage(error);
|
||||
// this.setState({ loading: false });
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Navigates to a LinkedIn webview modal, when the auth creds are fetched successfully it will execute onSuccess
|
||||
// async openLinkedInWebView() {
|
||||
// console.log('linkedWebview Func hit');
|
||||
// this.setState({ loading: true });
|
||||
|
||||
// try {
|
||||
// const result = await this.getLinkedInAuthURL();
|
||||
// this.setState({ loading: false });
|
||||
// console.log('navigating to linkedin webview');
|
||||
// this.props.navigation.navigate('linkedInWebView', { result, onSuccess: this.getLinkedInData });
|
||||
// } catch (error) {
|
||||
// showFlashErrorMessage(error);
|
||||
// this.setState({ loading: false });
|
||||
// }
|
||||
// }
|
||||
@ -1,284 +0,0 @@
|
||||
/** Screen for creating default User Profile **/
|
||||
import React from 'react';
|
||||
import { Icon, FormInput, Alert, Linking } from 'react-native-elements';
|
||||
import { Image, KeyboardAvoidingView, Platform, ScrollView, Text, View, Button, TouchableOpacity } from 'react-native';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import { strings } from '../../../locales/i18n';
|
||||
import { showFlashErrorMessage } from '../../../utils/errorHandler';
|
||||
import { fetchAuth } from '../../../utils/fetch';
|
||||
|
||||
import endpoints from '../../../config/apiEndpoints';
|
||||
import { toQueryString } from '../../../utils/aux';
|
||||
import { FormLabel, Spacer, TextButton } from '../../../components/common';
|
||||
import styles from './styles';
|
||||
|
||||
export default class SignupScreenFour extends React.Component {
|
||||
static navigationOptions = {
|
||||
title: strings('userCreateFlow.defaultProfileCreate.title'),
|
||||
headerStyle: { borderBottomWidth: 0 },
|
||||
headerTintColor: '#0D497E',
|
||||
headerRight: <Text style={styles.navigationHeaderRightStyle}>4/6</Text>
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
base64Img: null,
|
||||
cameraRollStatus: '',
|
||||
cameraStatus: '',
|
||||
loading: false,
|
||||
jobTitle: '',
|
||||
companyName: '',
|
||||
companyAddress: '',
|
||||
companyNumber: ''
|
||||
|
||||
};
|
||||
|
||||
this.openLinkedInWebView = this.openLinkedInWebView.bind(this);
|
||||
this.getLinkedInData = this.getLinkedInData.bind(this);
|
||||
this.getLinkedInAuthURL = this.getLinkedInAuthURL.bind(this);
|
||||
this.renderAvatarPhoto = this.renderAvatarPhoto.bind(this);
|
||||
this.launchPicker = this.launchPicker.bind(this);
|
||||
}
|
||||
|
||||
onSaveButtonPress() {
|
||||
//this will be replaced with realm once it has been added so that we can persist the user data
|
||||
//and send the object in this request
|
||||
this.props.navigation.navigate('signup5', {
|
||||
jobTitle: this.state.jobTitle,
|
||||
companyName: this.state.companyName,
|
||||
companyAddress: this.state.companyAddress,
|
||||
companyNumber: this.state.companyNumber
|
||||
});
|
||||
}
|
||||
|
||||
async onImageButtonPress(pickerMethod, pickerTypeState) {
|
||||
try {
|
||||
if (this.state[pickerTypeState] !== 'authorized') {
|
||||
const response = await Permissions.request('photo');
|
||||
if (response !== 'authorized') {
|
||||
Alert.alert(strings(`common.error.permissionDenied_camera${pickerTypeState === 'cameraRollStatus' ? 'Roll' : ''}`));
|
||||
Linking.openURL('app-settings:');
|
||||
this.setState({ [pickerTypeState]: response });
|
||||
return;
|
||||
} else if (response === 'authorized') {
|
||||
const status = await Permissions.check('photo');
|
||||
await this.launchPicker(pickerMethod, pickerTypeState, status);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Network request to our API to get an OAuth token
|
||||
async getLinkedInAuthURL() {
|
||||
console.log('getting linkedin auth info');
|
||||
this.setState({ loading: true });
|
||||
console.log('sending fetch request');
|
||||
const result = await fetchAuth(endpoints.linkedInLogin);
|
||||
|
||||
if (result.error) {
|
||||
showFlashErrorMessage(result.error);
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async getLinkedInData(linkedInAuthData) {
|
||||
try {
|
||||
const result = await fetchAuth(endpoints.retrieveLinkedInInfo(toQueryString(linkedInAuthData)));
|
||||
if (result.error) {
|
||||
showFlashErrorMessage(result.error);
|
||||
} else {
|
||||
//this call will be altered once we gefull linkedin partner api access
|
||||
//this.setState({ profileInfo: result, loading: false });
|
||||
}
|
||||
} catch (error) {
|
||||
showFlashErrorMessage(error);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
// Navigates to a LinkedIn webview modal, when the auth creds are fetched successfully it will execute onSuccess
|
||||
async openLinkedInWebView() {
|
||||
console.log('linkedWebview Func hit');
|
||||
this.setState({ loading: true });
|
||||
|
||||
try {
|
||||
const result = await this.getLinkedInAuthURL();
|
||||
this.setState({ loading: false });
|
||||
console.log('navigating to linkedin webview');
|
||||
this.props.navigation.navigate('linkedInWebView', { result, onSuccess: this.getLinkedInData });
|
||||
} catch (error) {
|
||||
showFlashErrorMessage(error);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
async launchPicker(pickerMethod, pickerTypeState, status) {
|
||||
await ImagePicker[pickerMethod]({
|
||||
allowsEditing: true,
|
||||
mediaTypes: 'photo'
|
||||
}, (libraryResponse) => {
|
||||
if (libraryResponse.didCancel) {
|
||||
console.log('User cancelled image picker');
|
||||
} else if (libraryResponse.error) {
|
||||
console.log('ImagePicker Error: ', libraryResponse.error);
|
||||
} else if (libraryResponse.customButton) {
|
||||
console.log('User tapped custom button: ', libraryResponse.customButton);
|
||||
}
|
||||
this.setState({
|
||||
base64Img: libraryResponse.data,
|
||||
[pickerTypeState]: status
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderLinkedInButtons() {
|
||||
return (
|
||||
<TextButton
|
||||
rightText={strings('common.contactFields.linkedIn_import')}
|
||||
rightTextStyle={{ fontSize: 20, fontWeight: 'normal', color: styles.colors.grey }}
|
||||
containerStyle={{ marginLeft: -2, marginTop: 15 }}
|
||||
onPress={this.openLinkedInWebView}
|
||||
disabled
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar cirle on profiles
|
||||
**/
|
||||
renderAvatarPhoto() {
|
||||
if (this.state.base64Img) {
|
||||
return (
|
||||
<View style={styles.avatarStyle}>
|
||||
<Image
|
||||
source={{ uri: this.state.base64Img }}
|
||||
style={styles.avatarStyle}
|
||||
/>
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// No profile Image
|
||||
return (
|
||||
<View style={styles.noAvatarStyle}>
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={150}
|
||||
color={styles.colors.lightTeal}
|
||||
/>
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the photo buttons
|
||||
**/
|
||||
renderPhotoButtons() {
|
||||
const self = this;
|
||||
return (
|
||||
<View style={styles.buttonContainerStyle}>
|
||||
<TouchableOpacity
|
||||
onPress={this.onImageButtonPress.bind(self, 'launchImageLibrary', 'cameraRollStatus')}
|
||||
disabled={this.props.loading}
|
||||
>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.uploadPhoto')}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={styles.photoButtonsOrTextStyle}>{strings('profile.or')}</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={this.onImageButtonPress.bind(self, 'launchCamera', 'cameraStatus')}
|
||||
disabled={this.props.loading}
|
||||
>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.takePhoto')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderInputComponents(labelString, inputPlaceholderText, onChangeInput) {
|
||||
return (
|
||||
<View>
|
||||
<FormLabel
|
||||
bold
|
||||
labelText={labelString}
|
||||
textStyle={styles.labelStyle}
|
||||
/>
|
||||
<FormInput
|
||||
underlineColorAndroid={styles.colors.greyBlue}
|
||||
placeholder={inputPlaceholderText}
|
||||
onChangeText={value => this.setState({ [onChangeInput]: value })}
|
||||
style={styles.inputTextStyle}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
renderContent() {
|
||||
return (
|
||||
<ScrollView contentContainerStyle={styles.parentContentScrollViewStyle} keyboardShouldPersistTaps="handled">
|
||||
<View style={{ flex: 1 }}>
|
||||
{/* INSTRUCTIONS */}
|
||||
<Text style={styles.instructions}>{strings('userCreateFlow.defaultProfileCreate.instructions')}</Text>
|
||||
<Spacer />
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
{this.renderAvatarPhoto()}
|
||||
<Spacer />
|
||||
{this.renderLinkedInButtons()}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_title_labelText'), strings('common.contactFields.company_title_placeholder'), 'jobTitle')}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_name_labelText'), strings('common.contactFields.company_name_placeholder'), 'companyName')}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_address_labelText'), strings('common.contactFields.company_address_placeholder'), 'companyAddress')}
|
||||
{this.renderInputComponents(strings('common.contactFields.company_number_labelText'), strings('common.contactFields.company_number_placeholder'), 'companyNumber')}
|
||||
<Text>{strings('userCreateFlow.defaultProfileCreate.no_verification_needed')}</Text>
|
||||
<Spacer />
|
||||
</View>
|
||||
</View>
|
||||
{/* SAVE AND CONTINUE BUTTON */}
|
||||
<Button
|
||||
onPress={this.onSaveButtonPress.bind(this)}
|
||||
title={strings('common.buttons.saveAndContinue')}
|
||||
loading={this.state.loading}
|
||||
style={styles.buttonStyle}
|
||||
fontSize={20}
|
||||
/>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
if (Platform.OS === 'android') {
|
||||
return this.renderContent();
|
||||
}
|
||||
|
||||
// IOS
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1 }}
|
||||
keyboardVerticalOffset={90}
|
||||
behavior="padding"
|
||||
>
|
||||
{this.renderContent()}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
import { SCREEN_WIDTH, colors } from '../../styleVars';
|
||||
|
||||
const styles = {
|
||||
logoContainerStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
logoStyle: {
|
||||
marginTop: 20,
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderWidth: 1,
|
||||
borderRadius: 60
|
||||
},
|
||||
buttonStyle: {
|
||||
width: SCREEN_WIDTH * 0.9,
|
||||
marginTop: 15,
|
||||
marginBottom: 30,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.buttonBlue
|
||||
},
|
||||
inputTextStyle: {
|
||||
marginLeft: 15,
|
||||
color: colors.black,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.black,
|
||||
},
|
||||
instructions: {
|
||||
fontSize: 16,
|
||||
fontFamily: 'Sans Serif',
|
||||
textAlign: 'left',
|
||||
color: colors.darkGrey,
|
||||
marginBottom: 5,
|
||||
marginHorizontal: 10
|
||||
},
|
||||
freeTextStyle: {
|
||||
left: 20,
|
||||
marginTop: 10,
|
||||
fontSize: 18
|
||||
},
|
||||
navigationHeaderRightStyle: {
|
||||
color: colors.middleGrey,
|
||||
marginRight: 5
|
||||
},
|
||||
parentContentScrollViewStyle: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
buttonContainerStyle: {
|
||||
flex: 2,
|
||||
marginTop: 15,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
photoButtonsOrTextStyle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: colors.lightGrey,
|
||||
marginTop: 5,
|
||||
marginBottom: 5,
|
||||
marginLeft: 15
|
||||
},
|
||||
avatarStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 25
|
||||
},
|
||||
noAvatarStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10
|
||||
},
|
||||
textButtonStyle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: colors.labelBlue,
|
||||
marginTop: 5,
|
||||
marginBottom: 5,
|
||||
marginLeft: 0
|
||||
},
|
||||
labelStyle: {
|
||||
fontWeight: 'bold',
|
||||
color: colors.labelBlue,
|
||||
marginTop: 5,
|
||||
marginLeft: 0
|
||||
},
|
||||
colors
|
||||
};
|
||||
|
||||
export default styles;
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Alert, Dimensions, Text, View, FlatList, TouchableOpacity, Platform } from 'react-native';
|
||||
import { Alert, Dimensions, Text, View, FlatList, TouchableOpacity, Platform, ActivityIndicator } from 'react-native';
|
||||
import { Button, CheckBox } from 'react-native-elements';
|
||||
import OpenAppSettings from 'react-native-app-settings';
|
||||
import Permissions from 'react-native-permissions';
|
||||
@ -7,12 +7,12 @@ import Contacts from 'react-native-contacts';
|
||||
|
||||
import { strings } from '../../locales/i18n';
|
||||
import { fetchAuth } from '../../utils/fetch';
|
||||
import { showFlashErrorMessage } from '../../utils/errorHandler';
|
||||
import { loadSignupState, saveSignupState, isContactSelected } from '../../realm/actions/AuthActions';
|
||||
|
||||
import colors from '../../config/colors.json';
|
||||
import errors from '../../config/errors';
|
||||
import endpoints from '../../config/apiEndpoints';
|
||||
import ContactListItem from '../../components/lists/ContactListItem';
|
||||
|
||||
import ContactListItem from '../../components/new/ContactListItem';
|
||||
|
||||
export default class SignupScreenFive extends React.Component {
|
||||
static navigationOptions = {
|
||||
@ -26,54 +26,71 @@ export default class SignupScreenFive extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
allContacts: [],
|
||||
selectedContacts: new Map()
|
||||
selectedContacts: new Map(),
|
||||
isUpdate: false,
|
||||
loading: true
|
||||
};
|
||||
|
||||
loadSignupState(5).then(result => {
|
||||
this.isUpdate = result.isUpdate;
|
||||
this.setState({ ...result.prevState }, () => this.fetchContacts());
|
||||
});
|
||||
|
||||
this.onSelectAll = this.onSelectAll.bind(this);
|
||||
this.fetchContacts();
|
||||
this.onSaveButtonPress = this.onSaveButtonPress.bind(this);
|
||||
}
|
||||
|
||||
// Handles when the user presses on the select all button
|
||||
onSelectAll() {
|
||||
const { allContacts } = this.state;
|
||||
const selectedContacts = new Map();
|
||||
if (this.state.selectedContacts.size === 0) {
|
||||
for (let i = 0; i < allContacts.length; i++) {
|
||||
selectedContacts.set(i, allContacts[i].phoneNumbers);
|
||||
}
|
||||
const { allContacts, selectedContacts } = this.state;
|
||||
if (allContacts.length === selectedContacts.size) {
|
||||
this.setState({ selectedContacts: new Map() });
|
||||
} else {
|
||||
this.setState({ selectedContacts: this.selectAllContacts(allContacts) });
|
||||
}
|
||||
|
||||
this.setState({ selectedContacts });
|
||||
}
|
||||
|
||||
// When a user clicks on a single contact
|
||||
onSelectContact(index) {
|
||||
onSelectContact(recordID, index) {
|
||||
this.setState((state) => {
|
||||
const selectedContacts = new Map(state.selectedContacts);
|
||||
if (selectedContacts.has(index)) {
|
||||
selectedContacts.delete(index);
|
||||
|
||||
if (selectedContacts.has(recordID)) {
|
||||
selectedContacts.delete(recordID);
|
||||
} else {
|
||||
const phoneNumbers = state.allContacts[index].phoneNumbers;
|
||||
selectedContacts.set(index, phoneNumbers);
|
||||
const phoneNumbers = this.state.allContacts[index].phoneNumbers;
|
||||
selectedContacts.set(recordID, phoneNumbers);
|
||||
}
|
||||
|
||||
return { selectedContacts };
|
||||
});
|
||||
}
|
||||
|
||||
// Sends the request and navigates the user to next page.
|
||||
onSaveButtonPress() {
|
||||
// Sends the request and navigates the user to next page.
|
||||
// Saves the users selected contacts and saves the request result in realm to be processed later.
|
||||
async onSaveButtonPress() {
|
||||
const { selectedContacts } = this.state;
|
||||
saveSignupState(-5, selectedContacts.keys());
|
||||
if (selectedContacts.size === 0) {
|
||||
this.props.navigation.navigate('signup6');
|
||||
return saveSignupState(5, { tagferIds: [], phoneNumbers: [] }, this.isUpdate);
|
||||
}
|
||||
|
||||
this.setState({ loading: true });
|
||||
const phoneNumbers = [];
|
||||
for (const record of selectedContacts.values()) {
|
||||
phoneNumbers.push(...record.map(entry => entry.number));
|
||||
}
|
||||
|
||||
fetchAuth(endpoints.findUsersByPhone, { phoneNumbers })
|
||||
.then(res => console.log(res))
|
||||
.catch(() => console.log(errors.APP_NETWORK_ERROR));
|
||||
const resp = await fetchAuth(endpoints.findUsersByPhone, { phoneNumbers });
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (resp.error) {
|
||||
return showFlashErrorMessage(resp.error);
|
||||
}
|
||||
|
||||
this.props.navigation.navigate('signup6');
|
||||
saveSignupState(5, { tagferIds: resp.inNetwork, phoneNumbers: resp.outNetwork }, this.isUpdate);
|
||||
}
|
||||
|
||||
// BEGIN: Fetch contacts
|
||||
@ -82,12 +99,8 @@ export default class SignupScreenFive extends React.Component {
|
||||
if (error) {
|
||||
this.props.navigation.navigate('signup6');
|
||||
} else {
|
||||
const selectedContacts = new Map();
|
||||
for (let i = 0; i < contacts.length; i++) {
|
||||
selectedContacts.set(i, contacts[i].phoneNumbers);
|
||||
}
|
||||
|
||||
this.setState({ allContacts: contacts, selectedContacts });
|
||||
const selectedContacts = this.isUpdate ? this.restoreSelectedContacts(contacts) : this.selectAllContacts(contacts);
|
||||
this.setState({ allContacts: contacts, selectedContacts, loading: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -99,6 +112,26 @@ export default class SignupScreenFive extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
selectAllContacts(contacts) {
|
||||
const selectedContacts = new Map();
|
||||
for (let i = 0; i < contacts.length; i++) {
|
||||
selectedContacts.set(contacts[i].recordID, contacts[i].phoneNumbers);
|
||||
}
|
||||
|
||||
return selectedContacts;
|
||||
}
|
||||
|
||||
restoreSelectedContacts(contacts) {
|
||||
const selectedContacts = new Map();
|
||||
for (let i = 0; i < contacts.length; i++) {
|
||||
if (isContactSelected(contacts[i].recordID)) {
|
||||
selectedContacts.set(contacts[i].recordID, contacts[i].phoneNumbers);
|
||||
}
|
||||
}
|
||||
|
||||
return selectedContacts;
|
||||
}
|
||||
|
||||
createSettingsAlert() {
|
||||
Alert.alert(
|
||||
'Contacts Access Denied',
|
||||
@ -106,7 +139,8 @@ export default class SignupScreenFive extends React.Component {
|
||||
[
|
||||
{ text: 'Cancel', onPress: () => this.props.navigation.navigate('signup6') },
|
||||
{ text: 'Settings', onPress: () => OpenAppSettings.open() }
|
||||
]
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
}
|
||||
|
||||
@ -127,8 +161,16 @@ export default class SignupScreenFive extends React.Component {
|
||||
// END: Fetch Contacts
|
||||
|
||||
renderItem(contact, index) {
|
||||
const selected = this.state.selectedContacts.has(index);
|
||||
return <ContactListItem contact={contact} selected={selected} onPress={() => this.onSelectContact(index)} />;
|
||||
const selected = this.state.selectedContacts.has(contact.recordID);
|
||||
return <ContactListItem contact={contact} selected={selected} onPress={() => this.onSelectContact(contact.recordID, index)} />;
|
||||
}
|
||||
|
||||
renderActivityIndicator() {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', marginTop: 20 }}>
|
||||
<ActivityIndicator size='large' color={colors.labelBlue} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -136,7 +178,7 @@ export default class SignupScreenFive extends React.Component {
|
||||
<View style={styles.container}>
|
||||
{/* INSTRUCTIONS */}
|
||||
<Text style={styles.instructions}>{strings('contact.importScreen_description')}</Text>
|
||||
|
||||
|
||||
{/* FLAT LIST OF CONTACTS */}
|
||||
<FlatList
|
||||
ListHeaderComponent={<ListHeader isChecked={this.state.selectedContacts.size === this.state.allContacts.length} onPress={this.onSelectAll} onSkip={() => this.props.navigation.navigate('signup6')} />}
|
||||
@ -145,12 +187,15 @@ export default class SignupScreenFive extends React.Component {
|
||||
renderItem={({ item, index }) => this.renderItem(item, index)}
|
||||
keyExtractor={(item) => item.recordID}
|
||||
style={{ flex: 1, marginHorizontal: 10 }}
|
||||
ListEmptyComponent={this.renderActivityIndicator()}
|
||||
/>
|
||||
|
||||
{/* SAVE AND CONTINUE BUTTON */}
|
||||
<Button
|
||||
onPress={this.onSaveButtonPress.bind(this)}
|
||||
onPress={this.onSaveButtonPress}
|
||||
title={strings('common.buttons.saveAndContinue')}
|
||||
loading={this.state.loading}
|
||||
disabled={this.state.loading}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
fontSize={20}
|
||||
iconRight={styles.chevronIconStyle}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Dimensions, Text, View, FlatList, TouchableOpacity } from 'react-native';
|
||||
import { Alert, Dimensions, Text, View, FlatList, TouchableOpacity } from 'react-native';
|
||||
import { Button } from 'react-native-elements';
|
||||
|
||||
import { strings } from '../../locales/i18n';
|
||||
@ -11,6 +11,7 @@ import ListActivityIndicator from '../../components/new/ListActivityIndicator';
|
||||
import colors from '../../config/colors.json';
|
||||
import errors from '../../config/errors';
|
||||
import endpoints from '../../config/apiEndpoints';
|
||||
import { loadSignupState, saveAuthState } from '../../realm/actions/AuthActions';
|
||||
|
||||
export default class SignupScreenSix extends React.Component {
|
||||
static navigationOptions = {
|
||||
@ -24,17 +25,22 @@ export default class SignupScreenSix extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
users: [],
|
||||
selectedUsers: new Map()
|
||||
selectedUsers: new Map(),
|
||||
loading: true
|
||||
};
|
||||
|
||||
this.onLoad();
|
||||
this.onSignup = this.onSignup.bind(this);
|
||||
}
|
||||
|
||||
// Load suggested contacts
|
||||
onLoad() {
|
||||
fetchAuth(endpoints.suggestProfiles)
|
||||
.then(result => this.setState({ users: result.profiles }))
|
||||
.catch(() => showFlashErrorMessage(errors.APP_NETWORK_ERROR));
|
||||
async onLoad() {
|
||||
const resp = await fetchAuth(endpoints.suggestProfiles);
|
||||
this.setState({ loading: false, users: resp.profiles });
|
||||
|
||||
if (resp.error) {
|
||||
showFlashErrorMessage(errors.APP_NETWORK_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// Add user to selected map
|
||||
@ -51,11 +57,61 @@ export default class SignupScreenSix extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
// Implement the signup functionality
|
||||
// TODO: Add all the Realm handlers to handle saving the data
|
||||
onSignup() {
|
||||
console.log('Unimplemented Signup');
|
||||
console.log([...this.state.selectedUsers.values()]);
|
||||
async onSignup() {
|
||||
this.setState({ loading: true });
|
||||
const { prevState } = await loadSignupState(6);
|
||||
const { user, profile, invites } = prevState;
|
||||
const invitesObject = this.createInvitesObject(invites, this.state.selectedUsers.values());
|
||||
const requestBody = this.createSignupRequest(user, profile, invitesObject);
|
||||
console.log(requestBody);
|
||||
const resp = await fetchAuth(endpoints.signup, requestBody);
|
||||
this.setState({ loading: false });
|
||||
|
||||
if (resp.error) {
|
||||
showFlashErrorMessage(resp.error);
|
||||
} else {
|
||||
saveAuthState(resp.sessionId);
|
||||
Alert.alert('Signup Success 🎉', 'Check back next week for more features.');
|
||||
}
|
||||
}
|
||||
|
||||
createSignupRequest(user, profile, invites) {
|
||||
const { email, password, firstName, lastName, tagferId, formattedPhoneNumber } = user;
|
||||
const { jobTitle, companyName, companyEmail, companyPhoneNumber, photoBytes } = profile;
|
||||
return {
|
||||
user: {
|
||||
email,
|
||||
password,
|
||||
tagferId,
|
||||
phoneNumber: formattedPhoneNumber
|
||||
},
|
||||
profile: {
|
||||
fullName: `${firstName} ${lastName}`,
|
||||
jobTitle,
|
||||
companyName,
|
||||
companyEmail,
|
||||
companyPhoneNumber,
|
||||
photoBytes
|
||||
},
|
||||
invites: {
|
||||
phoneNumbers: invites.phoneNumbers,
|
||||
requests: invites.tagferIds
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
createInvitesObject(invites, suggested) {
|
||||
const { tagferIds, phoneNumbers } = invites;
|
||||
const set = new Set();
|
||||
|
||||
// Avoids sending duplicate tagferIds
|
||||
for (const tagferId of suggested) { set.add(tagferId); }
|
||||
for (let i = 0; i < tagferIds.length; i++) { set.add(tagferIds[i]); }
|
||||
|
||||
return {
|
||||
tagferIds: [...set.values()],
|
||||
phoneNumbers: [...phoneNumbers.values()]
|
||||
};
|
||||
}
|
||||
|
||||
// Render a single entry in the list
|
||||
@ -82,10 +138,11 @@ export default class SignupScreenSix extends React.Component {
|
||||
|
||||
{/* SIGNUP BUTTON */}
|
||||
<Button
|
||||
onPress={this.onSignup.bind(this)}
|
||||
onPress={this.onSignup}
|
||||
title={strings('userCreateFlow.signUp.title')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
loading={this.state.loading}
|
||||
disabled={this.state.loading}
|
||||
fontSize={20}
|
||||
/>
|
||||
</View>
|
||||
|
||||
91
src/screens/auth/styles.js
Normal file
@ -0,0 +1,91 @@
|
||||
import { SCREEN_WIDTH, colors } from '../styleVars';
|
||||
|
||||
const styles = {
|
||||
logoContainerStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
logoStyle: {
|
||||
marginTop: 20,
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderWidth: 1,
|
||||
borderRadius: 60
|
||||
},
|
||||
buttonStyle: {
|
||||
width: SCREEN_WIDTH * 0.9,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.buttonBlue
|
||||
},
|
||||
inputTextStyle: {
|
||||
color: colors.black
|
||||
},
|
||||
instructions: {
|
||||
fontSize: 16,
|
||||
textAlign: 'left',
|
||||
color: colors.darkGrey,
|
||||
marginBottom: 5,
|
||||
marginHorizontal: 10
|
||||
},
|
||||
freeTextStyle: {
|
||||
left: 20,
|
||||
marginTop: 10,
|
||||
fontSize: 18
|
||||
},
|
||||
navigationHeaderRightStyle: {
|
||||
color: colors.middleGrey,
|
||||
marginRight: 5
|
||||
},
|
||||
parentContentScrollViewStyle: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
buttonContainerStyle: {
|
||||
flex: 2,
|
||||
marginLeft: 30,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
photoButtonsOrTextStyle: {
|
||||
fontSize: 22,
|
||||
color: colors.lightGrey
|
||||
},
|
||||
avatarStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#FAFAFA'
|
||||
},
|
||||
noAvatarStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10
|
||||
},
|
||||
textButtonStyle: {
|
||||
fontSize: 20,
|
||||
color: colors.labelBlue,
|
||||
marginTop: 5,
|
||||
marginBottom: 5,
|
||||
marginLeft: 0
|
||||
},
|
||||
labelStyle: {
|
||||
color: colors.labelBlue,
|
||||
marginTop: 5,
|
||||
marginLeft: 0
|
||||
},
|
||||
chevronIconStyle: {
|
||||
name: 'chevron-right',
|
||||
color: colors.white,
|
||||
size: 30,
|
||||
style: { marginLeft: 0 }
|
||||
},
|
||||
colors
|
||||
};
|
||||
|
||||
export default styles;
|
||||
@ -1,4 +1,5 @@
|
||||
import appConfig from '../config/appConfig.json';
|
||||
import errors from '../config/errors.js';
|
||||
|
||||
export function fetchAuth(endpoint, body) {
|
||||
const request = {
|
||||
@ -10,5 +11,5 @@ export function fetchAuth(endpoint, body) {
|
||||
body: JSON.stringify(body)
|
||||
};
|
||||
|
||||
return fetch(endpoint.url, request).then(res => res.json());
|
||||
return fetch(endpoint.url, request).then(res => res.json()).catch(() => ({ error: errors.APP_NETWORK_ERROR }));
|
||||
}
|
||||
|
||||