mirror of
https://bitbucket.org/tagfer_team/tagfer-app.git
synced 2025-12-25 03:37:41 +00:00
UPS11-Accept/Decline Connection Requests (pull request #7)
This commit is contained in:
parent
54d9a76131
commit
5263e032b4
1
.gitignore
vendored
1
.gitignore
vendored
@ -62,3 +62,4 @@ storybook
|
||||
test.js
|
||||
ios/Tagfer.xcodeproj/project.pbxproj
|
||||
package-lock.json
|
||||
src/screens/contacts/data.js
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>3</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
||||
@ -8,6 +8,7 @@ const contactsTabBarOptions = {
|
||||
upperCaseLabel: false,
|
||||
activeTintColor: 'white',
|
||||
inactiveTintColor: '#53ACF0',
|
||||
lazy: true,
|
||||
labelStyle: {
|
||||
margin: 1,
|
||||
fontSize: 13,
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Avatar } from 'react-native-elements';
|
||||
|
||||
import { isEmpty } from '../../utils/aux';
|
||||
|
||||
const TagferAvatar = ({ size, photoURL, style, color }) => {
|
||||
const uri = !isEmpty(photoURL) ? { uri: photoURL } : null;
|
||||
const icon = isEmpty(photoURL) ? { name: 'user', type: 'entypo' } : null;
|
||||
const uri = photoURL ? { uri: photoURL } : null;
|
||||
const icon = !photoURL ? { name: 'user', type: 'entypo' } : null;
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
|
||||
@ -13,7 +13,7 @@ const UserListItem = ({ profile, selected, onPress }) => {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
avatar={<TagferAvatar size='medium' photoURL={photoURL} />}
|
||||
avatar={<TagferAvatar size='medium' photoURL={photoURL} color={colors.lightGrey} />}
|
||||
title={fullName}
|
||||
subtitle={subtitle}
|
||||
subtitleNumberOfLines={3}
|
||||
@ -24,5 +24,4 @@ const UserListItem = ({ profile, selected, onPress }) => {
|
||||
/>);
|
||||
};
|
||||
|
||||
export default UserListItem
|
||||
;
|
||||
export default UserListItem;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const baseurl = 'https://us-central1-tagfer-inc.cloudfunctions.net/api';
|
||||
const baseurl = 'http://localhost:3000';
|
||||
const endpoints = {
|
||||
login: {
|
||||
method: 'POST',
|
||||
@ -39,6 +39,22 @@ const endpoints = {
|
||||
signup: {
|
||||
method: 'PUT',
|
||||
url: `${baseurl}/auth/signup`
|
||||
},
|
||||
getConnectionRequests: {
|
||||
method: 'GET',
|
||||
url: `${baseurl}/connections/me/requests`
|
||||
},
|
||||
sendConnectionRequest: (profileN) => ({
|
||||
method: 'PUT',
|
||||
url: `${baseurl}/connections/me/${profileN}`
|
||||
}),
|
||||
acceptConnectionRequest: (profileN) => ({
|
||||
method: 'POST',
|
||||
url: `${baseurl}/connections/me/${profileN}`
|
||||
}),
|
||||
removeConnectionRequest: {
|
||||
method: 'DELETE',
|
||||
url: `${baseurl}/connections/me`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
import { createStackNavigator, createAppContainer } from 'react-navigation';
|
||||
import WelcomeScreen from '../screens/onboaring/WelcomeScreen';
|
||||
// AUTH IMPORTS
|
||||
import LoginScreen from '../screens/auth/LoginScreen';
|
||||
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';
|
||||
import SignupScreenFive from '../screens/auth/SignupScreen5';
|
||||
import SignupScreenSix from '../screens/auth/SignupScreen6';
|
||||
import TwitterWebView from '../screens/auth/TwitterWebView';
|
||||
import LinkedInWebView from '../screens/auth/LinkedInWebView';
|
||||
import ForgotPasswordScreen from '../screens/auth/ForgotPasswordScreen';
|
||||
// PROFILE IMPORTS
|
||||
import ProfileQRCodeScanner from '../screens/profile/ProfileQRCodeScanner';
|
||||
import ProfileSearchScreen from '../screens/profile/ProfileSearchScreen';
|
||||
|
||||
const MainNavigator = createStackNavigator({
|
||||
welcome: WelcomeScreen,
|
||||
login: LoginScreen,
|
||||
forgotPassword: ForgotPasswordScreen,
|
||||
signup1: SignupScreenOne,
|
||||
signup2: SignupScreenTwo,
|
||||
signup3a: SignupScreenThreeA,
|
||||
signup3b: SignupScreenThreeB,
|
||||
signup4: SignupScreenFour,
|
||||
signup5: SignupScreenFive,
|
||||
signup6: SignupScreenSix,
|
||||
profileQRScan: ProfileQRCodeScanner,
|
||||
profileSearch: ProfileSearchScreen
|
||||
});
|
||||
|
||||
const RootNavigator = createStackNavigator(
|
||||
{
|
||||
Main: {
|
||||
screen: MainNavigator,
|
||||
navigationOptions: { header: null }
|
||||
},
|
||||
twitterWebView: TwitterWebView,
|
||||
linkedInWebView: LinkedInWebView
|
||||
},
|
||||
{
|
||||
mode: 'modal'
|
||||
});
|
||||
|
||||
const AppNavigator = createAppContainer(RootNavigator);
|
||||
|
||||
export default AppNavigator;
|
||||
@ -6,6 +6,7 @@ import { UserSchema, ProfileSchema, InvitesSchema, ContactsSchema, SignupConfig,
|
||||
|
||||
var authRealm;
|
||||
var signupRealm;
|
||||
var sessionId;
|
||||
|
||||
// Loads the signup state; a `then` should be chained at the component level to save the state
|
||||
export async function loadSignupState(screen) {
|
||||
@ -37,9 +38,15 @@ export async function saveAuthState(sessionId) {
|
||||
}
|
||||
|
||||
export async function getAuthState() {
|
||||
if (!!sessionId) {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
try {
|
||||
const realm = await getRealm(0);
|
||||
return realm.objectForPrimaryKey(SessionSchema.name, primaryKey);
|
||||
const auth = realm.objectForPrimaryKey(SessionSchema.name, primaryKey);
|
||||
sessionId = !!auth ? auth.sessionId : undefined;
|
||||
return sessionId;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@ -1,21 +1,265 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { View, Text, TouchableOpacity, FlatList } from 'react-native';
|
||||
import { ListItem, Icon } from 'react-native-elements';
|
||||
|
||||
import { fetchTagferApi } from '../../utils/fetch';
|
||||
|
||||
import TagferAvatar from '../../components/new/TagferAvatar';
|
||||
import colors from '../../config/colors.json';
|
||||
import endpoints from '../../config/apiEndpoints';
|
||||
import { showFlashErrorMessage } from '../../utils/errorHandler';
|
||||
|
||||
export default class RequestsScreen extends React.Component {
|
||||
static navigationOptions = ({ navigation }) => {
|
||||
const title = navigation.getParam('title', 'Unconfirmed');
|
||||
return { title };
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filter: 'rcvd',
|
||||
currRequests: [],
|
||||
loading: false
|
||||
};
|
||||
|
||||
this.rcvdRequests = [];
|
||||
this.sentRequests = [];
|
||||
this.onRcvdPress = this.onRcvdPress.bind(this);
|
||||
this.onSentPress = this.onSentPress.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.onLoad();
|
||||
}
|
||||
|
||||
onRcvdPress() {
|
||||
this.setState({ filter: 'rcvd', currRequests: this.rcvdRequests });
|
||||
}
|
||||
|
||||
onSentPress() {
|
||||
this.setState({ filter: 'sent', currRequests: this.sentRequests });
|
||||
}
|
||||
|
||||
onAccept(index) {
|
||||
const fromTagferId = this.rcvdRequests[index].tagferId;
|
||||
const fromProfileN = this.rcvdRequests[index].profileN;
|
||||
const response = fetchTagferApi(endpoints.acceptConnectionRequest(1), { fromTagferId, fromProfileN });
|
||||
|
||||
if (response.error) {
|
||||
return showFlashErrorMessage(response.error);
|
||||
}
|
||||
|
||||
this.rcvdRequests.splice(index, 1);
|
||||
this.setState({ currRequests: this.rcvdRequests });
|
||||
this.updateTitle();
|
||||
}
|
||||
|
||||
async onReject(index) {
|
||||
const { filter } = this.state;
|
||||
const fromTagferId = filter === 'rcvd' ? this.rcvdRequests[index].tagferId : undefined;
|
||||
const toTagferId = filter === 'sent' ? this.sentRequests[index].tagferId : undefined;
|
||||
const response = await fetchTagferApi(endpoints.removeConnectionRequest, { fromTagferId, toTagferId });
|
||||
|
||||
if (response.error) {
|
||||
return showFlashErrorMessage(response.error);
|
||||
}
|
||||
|
||||
if (this.state.filter === 'rcvd') {
|
||||
this.rcvdRequests.splice(index, 1);
|
||||
this.setState({ currRequests: this.rcvdRequests });
|
||||
} else {
|
||||
this.sentRequests.splice(index, 1);
|
||||
this.setState({ currRequests: this.sentRequests });
|
||||
}
|
||||
|
||||
this.updateTitle();
|
||||
}
|
||||
|
||||
async onLoad() {
|
||||
this.setState({ loading: true });
|
||||
const response = await fetchTagferApi(endpoints.getConnectionRequests);
|
||||
|
||||
if (response.error) {
|
||||
this.setState({ loading: false });
|
||||
return showFlashErrorMessage(response.error);
|
||||
}
|
||||
|
||||
this.rcvdRequests = response.received;
|
||||
this.sentRequests = response.sent;
|
||||
this.updateTitle();
|
||||
|
||||
const currRequests = this.state.filter === 'rcvd' ? this.rcvdRequests : this.sentRequests;
|
||||
this.setState({ currRequests, loading: false });
|
||||
}
|
||||
|
||||
updateTitle() {
|
||||
let numOfRequests = this.rcvdRequests.length + this.sentRequests.length;
|
||||
if (numOfRequests > 100) { numOfRequests = '99+'; }
|
||||
|
||||
this.props.navigation.setParams({ title: `Unconfirmed(${numOfRequests})` });
|
||||
}
|
||||
|
||||
// Render a single entry in the list
|
||||
renderItem(profile, index) {
|
||||
return (
|
||||
<RequestListItem
|
||||
type={this.state.filter}
|
||||
profile={profile}
|
||||
onAccept={() => this.onAccept(index)}
|
||||
onReject={() => this.onReject(index)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default class UnconfirmedConnetcionsScreen extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Unconfirmed Connections</Text>
|
||||
<FilterButtons
|
||||
filter={this.state.filter}
|
||||
rcvdCount={this.rcvdRequests.length}
|
||||
sentCount={this.sentRequests.length}
|
||||
onRcvdPress={this.onRcvdPress}
|
||||
onSentPress={this.onSentPress}
|
||||
/>
|
||||
<FlatList
|
||||
data={this.state.currRequests}
|
||||
extraData={this.state}
|
||||
renderItem={({ item, index }) => this.renderItem(item, index)}
|
||||
keyExtractor={(item) => item.tagferId}
|
||||
style={{ flex: 1, marginHorizontal: 10, marginTop: 10, borderRadius: 5 }}
|
||||
refreshing={this.state.loading}
|
||||
onRefresh={() => this.onLoad()}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const FilterButtons = ({ filter, rcvdCount = 0, sentCount = 0, onRcvdPress, onSentPress }) => {
|
||||
let rcvdButtonStyle;
|
||||
let rcvdButtonText;
|
||||
let sentButtonStyle;
|
||||
let sentButtonText;
|
||||
|
||||
if (filter === 'sent') {
|
||||
rcvdButtonStyle = styles.rcvdButtonNotSelected;
|
||||
sentButtonStyle = styles.sentButtonSelected;
|
||||
rcvdButtonText = styles.notSelectedText;
|
||||
sentButtonText = styles.selectedText;
|
||||
} else {
|
||||
rcvdButtonStyle = styles.rcvdButtonSelected;
|
||||
sentButtonStyle = styles.sentButtonNotSelected;
|
||||
rcvdButtonText = styles.selectedText;
|
||||
sentButtonText = styles.notSelectedText;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 10 }}>
|
||||
<TouchableOpacity style={rcvdButtonStyle} onPress={onRcvdPress}>
|
||||
<Text style={rcvdButtonText}>Requests received ({rcvdCount})</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={sentButtonStyle} onPress={onSentPress}>
|
||||
<Text style={sentButtonText}>Requests sent ({sentCount})</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const RequestListItem = ({ type, profile, onAccept, onReject }) => {
|
||||
const { tagferId, fullName, jobTitle, companyName, photoURL } = profile;
|
||||
const subtitle = `@${tagferId}\n${jobTitle} at ${companyName}`;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
avatar={<TagferAvatar size='medium' photoURL={photoURL} color={colors.lightGrey} />}
|
||||
title={fullName}
|
||||
subtitle={subtitle}
|
||||
subtitleNumberOfLines={3}
|
||||
subtitleStyle={{ fontSize: 12, fontWeight: 'normal' }}
|
||||
titleStyle={{ fontSize: 16, color: 'black' }}
|
||||
rightIcon={<ActionButtons type={type} onAccept={onAccept} onReject={onReject} />}
|
||||
containerStyle={{ backgroundColor: 'white' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionButtons = ({ type, onAccept, onReject }) => {
|
||||
const sentActionButtons = (
|
||||
<Icon
|
||||
name='ios-close-circle-outline'
|
||||
type='ionicon' size={45}
|
||||
color='#7389AE'
|
||||
onPress={onReject}
|
||||
/>
|
||||
);
|
||||
|
||||
const rcvdActionButtons = (
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
<Icon
|
||||
name='ios-close-circle-outline'
|
||||
type='ionicon' size={45}
|
||||
color='#7389AE'
|
||||
containerStyle={{ marginRight: 10 }}
|
||||
onPress={onReject}
|
||||
/>
|
||||
<Icon
|
||||
name='ios-checkmark-circle-outline'
|
||||
type='ionicon' size={45}
|
||||
color={colors.addGreen}
|
||||
onPress={onAccept}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
return type === 'sent' ? sentActionButtons : rcvdActionButtons;
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.offWhite
|
||||
},
|
||||
rcvdButtonSelected: {
|
||||
marginLeft: 40,
|
||||
height: 30,
|
||||
paddingHorizontal: 15,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
borderRadius: 15,
|
||||
backgroundColor: '#AAAFC4'
|
||||
},
|
||||
rcvdButtonNotSelected: {
|
||||
marginLeft: 40,
|
||||
height: 30,
|
||||
paddingHorizontal: 15,
|
||||
justifyContent: 'center',
|
||||
borderRadius: 15
|
||||
},
|
||||
sentButtonSelected: {
|
||||
marginRight: 40,
|
||||
height: 30,
|
||||
paddingHorizontal: 15,
|
||||
justifyContent: 'center',
|
||||
borderRadius: 15,
|
||||
backgroundColor: '#AAAFC4'
|
||||
},
|
||||
sentButtonNotSelected: {
|
||||
marginRight: 40,
|
||||
height: 30,
|
||||
paddingHorizontal: 15,
|
||||
justifyContent: 'center',
|
||||
borderRadius: 15
|
||||
},
|
||||
selectedText: {
|
||||
fontSize: 13,
|
||||
fontWeight: 'bold',
|
||||
color: 'white'
|
||||
},
|
||||
notSelectedText: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
color: '#6A6A6A'
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,21 +1,85 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { View, FlatList } from 'react-native';
|
||||
|
||||
import { fetchAuth, fetchTagferApi } from '../../utils/fetch';
|
||||
import { showFlashErrorMessage } from '../../utils/errorHandler';
|
||||
|
||||
import colors from '../../config/colors.json';
|
||||
import endpoints from '../../config/apiEndpoints';
|
||||
import UserListItem from '../../components/new/UserListItem';
|
||||
|
||||
export default class SuggestedContactsScreen extends React.Component {
|
||||
static navigationOptions = ({ navigation }) => {
|
||||
const title = navigation.getParam('title', 'Suggested');
|
||||
return { title };
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
users: [],
|
||||
loading: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.onLoad();
|
||||
}
|
||||
|
||||
// Load suggested contacts
|
||||
async onLoad() {
|
||||
const resp = await fetchAuth(endpoints.suggestProfiles);
|
||||
this.setState({ loading: false, users: resp.profiles || [] });
|
||||
|
||||
if (resp.error) {
|
||||
return showFlashErrorMessage(resp.error);
|
||||
}
|
||||
|
||||
this.updateTitle();
|
||||
}
|
||||
|
||||
// WARN: this might break as we are mutating state
|
||||
async onConnect(index) {
|
||||
const response = await fetchTagferApi(endpoints.sendConnectionRequest(1), { toTagferId: this.state.users[index].tagferId });
|
||||
if (response.error) {
|
||||
return showFlashErrorMessage(response.error);
|
||||
}
|
||||
|
||||
this.state.users.splice(index, 1);
|
||||
this.updateTitle();
|
||||
|
||||
if (this.state.users.length === 0) {
|
||||
this.onLoad();
|
||||
}
|
||||
}
|
||||
|
||||
// Update count in title
|
||||
updateTitle() {
|
||||
let count = this.state.users.length;
|
||||
if (count > 100) { count = '99+'; }
|
||||
|
||||
this.props.navigation.setParams({ title: `Suggested(${count})` });
|
||||
}
|
||||
|
||||
// Render a single entry in the list
|
||||
renderItem(profile, index) {
|
||||
return <UserListItem profile={profile} onPress={() => this.onConnect(index)} />;
|
||||
}
|
||||
|
||||
export default class SuggestedConnectionsScreen extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Suggested Connections</Text>
|
||||
<View style={{ flex: 1, backgroundColor: colors.offWhite }}>
|
||||
{/* SUGGESTED CONTACTS */}
|
||||
<FlatList
|
||||
data={this.state.users}
|
||||
extraData={this.state}
|
||||
renderItem={({ item, index }) => this.renderItem(item, index)}
|
||||
keyExtractor={(item) => item.tagferId}
|
||||
style={{ flex: 1, marginHorizontal: 10, marginTop: 10, borderRadius: 2 }}
|
||||
refreshing={this.state.loading}
|
||||
onRefresh={() => this.onLoad()}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import appConfig from '../config/appConfig.json';
|
||||
import errors from '../config/errors.js';
|
||||
|
||||
import { getAuthState } from '../realm/actions/AuthActions';
|
||||
|
||||
export function fetchAuth(endpoint, body) {
|
||||
const request = {
|
||||
method: endpoint.method,
|
||||
@ -13,3 +15,20 @@ export function fetchAuth(endpoint, body) {
|
||||
|
||||
return fetch(endpoint.url, request).then(res => res.json()).catch(() => ({ error: errors.APP_NETWORK_ERROR }));
|
||||
}
|
||||
|
||||
|
||||
// Fetch wrapper for authorized calls with valid session id. Returns either a JSON object documented in the wiki or an error.
|
||||
export async function fetchTagferApi(endpoint, body) {
|
||||
const sessionId = await getAuthState();
|
||||
|
||||
const request = {
|
||||
method: endpoint.method,
|
||||
headers: {
|
||||
Authorization: sessionId,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
};
|
||||
|
||||
return fetch(endpoint.url, request).then(res => res.json()).catch(() => ({ error: errors.APP_NETWORK_ERROR }));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user