aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/common/SocialIcon.tsx3
-rw-r--r--src/components/common/SocialLinkModal.tsx118
-rw-r--r--src/components/common/index.ts1
-rw-r--r--src/components/onboarding/SocialMediaLinker.tsx112
-rw-r--r--src/components/taggs/Tagg.tsx146
-rw-r--r--src/components/taggs/TaggsBar.tsx101
6 files changed, 307 insertions, 174 deletions
diff --git a/src/components/common/SocialIcon.tsx b/src/components/common/SocialIcon.tsx
index a46b1445..84da1ca7 100644
--- a/src/components/common/SocialIcon.tsx
+++ b/src/components/common/SocialIcon.tsx
@@ -22,6 +22,9 @@ const SocialIcon: React.FC<SocialIconProps> = ({
case 'Twitter':
var icon = require('../../assets/images/twitter-icon.png');
break;
+ case 'Tiktok':
+ var icon = require('../../assets/images/tiktok-icon.png');
+ break;
case 'Twitch':
var icon = require('../../assets/images/twitch-icon.png');
break;
diff --git a/src/components/common/SocialLinkModal.tsx b/src/components/common/SocialLinkModal.tsx
new file mode 100644
index 00000000..3cea2567
--- /dev/null
+++ b/src/components/common/SocialLinkModal.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+import {Modal, StyleSheet, Text, TouchableHighlight, View} from 'react-native';
+import {TextInput} from 'react-native-gesture-handler';
+import {SCREEN_WIDTH} from '../../utils';
+
+interface SocialLinkModalProps {
+ modalVisible: boolean;
+ setModalVisible: (_: boolean) => void;
+ completionCallback: (username: string) => void;
+}
+
+const SocialLinkModal: React.FC<SocialLinkModalProps> = ({
+ modalVisible,
+ setModalVisible,
+ completionCallback,
+}) => {
+ const [username, setUsername] = React.useState('');
+ return (
+ <>
+ <View style={styles.centeredView}>
+ <Modal
+ animationType="slide"
+ transparent={true}
+ visible={modalVisible}
+ onRequestClose={() => {}}>
+ <View style={styles.centeredView}>
+ <View style={styles.modalView}>
+ <TextInput
+ autoCapitalize={'none'}
+ autoCorrect={false}
+ textAlign={'center'}
+ placeholder={'Your username'}
+ style={styles.textInput}
+ onChangeText={setUsername}
+ value={username}
+ />
+ {/* link button */}
+ <TouchableHighlight
+ style={styles.openButton}
+ onPress={() => {
+ setModalVisible(!modalVisible);
+ setUsername('');
+ completionCallback(username);
+ }}>
+ <Text style={styles.textStyle}>Link</Text>
+ </TouchableHighlight>
+ {/* cancel button */}
+ <Text
+ onPress={() => {
+ setUsername('');
+ setModalVisible(!modalVisible);
+ }}
+ style={styles.cancelStyle}>
+ Cancel
+ </Text>
+ </View>
+ </View>
+ </Modal>
+ </View>
+ </>
+ );
+};
+
+const styles = StyleSheet.create({
+ centeredView: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 22,
+ },
+ modalView: {
+ width: (SCREEN_WIDTH * 2) / 3,
+ margin: 20,
+ backgroundColor: 'white',
+ borderRadius: 20,
+ padding: 35,
+ alignItems: 'center',
+ shadowColor: '#000',
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.25,
+ shadowRadius: 3.84,
+ elevation: 5,
+ },
+ openButton: {
+ borderRadius: 20,
+ padding: 10,
+ elevation: 2,
+ backgroundColor: '#2196F3',
+ },
+ textStyle: {
+ color: 'white',
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ cancelStyle: {
+ position: 'relative',
+ height: 17,
+ top: 17,
+ fontStyle: 'normal',
+ fontWeight: '500',
+ fontSize: 14,
+ /* identical to box height */
+ textAlign: 'center',
+ color: '#698DD3',
+ },
+ textInput: {
+ height: 20,
+ width: '75%',
+ borderBottomWidth: 0.4,
+ borderBottomColor: '#C4C4C4',
+ marginBottom: 20,
+ },
+});
+
+export default SocialLinkModal;
diff --git a/src/components/common/index.ts b/src/components/common/index.ts
index cd72a70b..61d826bd 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -9,4 +9,5 @@ export {default as TabsGradient} from './TabsGradient';
export {default as RecentSearches} from '../search/RecentSearches';
export {default as LoadingIndicator} from './LoadingIndicator';
export {default as DateLabel} from './DateLabel';
+export {default as SocialLinkModal} from './SocialLinkModal';
export * from './post';
diff --git a/src/components/onboarding/SocialMediaLinker.tsx b/src/components/onboarding/SocialMediaLinker.tsx
index 15afb731..da637f99 100644
--- a/src/components/onboarding/SocialMediaLinker.tsx
+++ b/src/components/onboarding/SocialMediaLinker.tsx
@@ -1,24 +1,14 @@
-import AsyncStorage from '@react-native-community/async-storage';
import React from 'react';
import {
- Alert,
Image,
StyleSheet,
Text,
TouchableOpacity,
TouchableOpacityProps,
} from 'react-native';
-import InAppBrowser from 'react-native-inappbrowser-reborn';
import {LinkerType} from 'src/types';
-import {
- LINK_FB_ENDPOINT,
- LINK_FB_OAUTH,
- LINK_IG_ENDPOINT,
- LINK_IG_OAUTH,
- LINK_TWITTER_ENDPOINT,
- LINK_TWITTER_OAUTH,
-} from '../../constants';
import {SOCIAL_FONT_COLORS} from '../../constants/constants';
+import {handlePressForAuthBrowser} from '../../services';
import SocialIcon from '../common/SocialIcon';
interface SocialMediaLinkerProps extends TouchableOpacityProps {
@@ -29,102 +19,14 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({
social: {label},
}) => {
const [state, setState] = React.useState({
- authenticated: false,
+ socialLinked: false,
});
- const integrated_endpoints: {[label: string]: [string, string]} = {
- Instagram: [LINK_IG_OAUTH, LINK_IG_ENDPOINT],
- Facebook: [LINK_FB_OAUTH, LINK_FB_ENDPOINT],
- Twitter: [LINK_TWITTER_OAUTH, LINK_TWITTER_ENDPOINT],
- };
-
- const registerSocialLink: (token: string) => Promise<boolean> = async (
- callback_url,
- ) => {
- if (!(label in integrated_endpoints)) {
- // This error is already handled earlier, more of a safety check here
- return false;
- }
- const user_token = await AsyncStorage.getItem('token');
- const response = await fetch(integrated_endpoints[label][1], {
- method: 'POST',
- headers: {
- Authorization: `Token ${user_token}`,
- },
- body: JSON.stringify({
- callback_url: callback_url,
- }),
- });
- if (!(response.status === 201)) {
- console.log(await response.json());
- }
- return response.status === 201;
- };
-
const handlePress = async () => {
- try {
- const isAvailable = await InAppBrowser.isAvailable();
- if (!(label in integrated_endpoints)) {
- // TODO handle non-integrated social links with a modal
- // TODO remove the alert below
- Alert.alert('Coming soon!');
- return;
- }
- let url = integrated_endpoints[label][0];
-
- // We will need to do an extra step for twitter sign-in
- if (label === 'Twitter') {
- const user_token = await AsyncStorage.getItem('token');
- const response = await fetch(url, {
- method: 'GET',
- headers: {
- Authorization: `Token ${user_token}`,
- },
- });
- url = response.url;
- }
-
- if (isAvailable) {
- InAppBrowser.openAuth(url, 'taggid://callback', {
- ephemeralWebSession: true,
- })
- .then(async (response) => {
- console.log(response);
- if (response.type === 'success' && response.url) {
- const success = await registerSocialLink(response.url);
- if (!success) {
- throw new Error('Unable to register with backend');
- }
- setState({
- ...state,
- authenticated: true,
- });
- Alert.alert(`Successfully linked ${label} 🎉`);
- } else {
- throw new Error(`Unable to link with ${label} API`);
- }
- })
- .catch((error) => {
- console.log(error);
- Alert.alert(`Something went wrong, we can't link with ${label} 😔`);
- });
- } else {
- // Okay... to open an external browser and have it link back to
- // the app is a bit tricky, we will need to have navigation routes
- // setup for this screen and have it hooked up.
- // See https://github.com/proyecto26/react-native-inappbrowser#authentication-flow-using-deep-linking
- // Though this isn't the end of the world, from the documentation,
- // the in-app browser should be supported from iOS 11, which
- // is about 98.5% of all iOS devices in the world.
- // See https://support.apple.com/en-gb/HT209574
- Alert.alert(
- 'Sorry! Your device was unable to open a browser to let you sign-in! 😔',
- );
- }
- } catch (error) {
- console.log(error);
- Alert.alert(`Something went wrong, we can't link with ${label} 😔`);
- }
+ setState({
+ ...state,
+ socialLinked: await handlePressForAuthBrowser(label),
+ });
};
switch (label) {
@@ -166,7 +68,7 @@ const SocialMediaLinker: React.FC<SocialMediaLinkerProps> = ({
style={styles.container}>
<SocialIcon social={label} style={styles.icon} />
<Text style={[styles.label, {color: font_color}]}>{label}</Text>
- {state.authenticated && (
+ {state.socialLinked && (
<Image
source={require('../../assets/images/link-tick.png')}
style={styles.tick}
diff --git a/src/components/taggs/Tagg.tsx b/src/components/taggs/Tagg.tsx
index 9274e0eb..c64da5ef 100644
--- a/src/components/taggs/Tagg.tsx
+++ b/src/components/taggs/Tagg.tsx
@@ -1,51 +1,141 @@
import {useNavigation} from '@react-navigation/native';
-import React from 'react';
-import {StyleSheet, TouchableOpacity, View} from 'react-native';
-import LinearGradient from 'react-native-linear-gradient';
-import {TAGGS_GRADIENT} from '../../constants';
+import React, {Fragment, useState} from 'react';
+import {Alert, Linking, StyleSheet, TouchableOpacity, View} from 'react-native';
+import PurpleRingPlus from '../../assets/icons/purple_ring+.svg';
+import PurpleRing from '../../assets/icons/purple_ring.svg';
+import RingPlus from '../../assets/icons/ring+.svg';
+import Ring from '../../assets/icons/ring.svg';
+import {INTEGRATED_SOCIAL_LIST, TAGG_ICON_DIM} from '../../constants';
+import {
+ handlePressForAuthBrowser,
+ registerNonIntegratedSocialLink,
+} from '../../services';
+import {SocialIcon, SocialLinkModal} from '../common';
interface TaggProps {
- style: object;
social: string;
isProfileView: boolean;
+ isLinked: boolean;
+ isIntegrated: boolean;
+ setTaggsNeedUpdate: (_: boolean) => void;
+ setSocialDataNeedUpdate: (_: string[]) => void;
}
-const Tagg: React.FC<TaggProps> = ({style, social, isProfileView}) => {
+const Tagg: React.FC<TaggProps> = ({
+ social,
+ isProfileView,
+ isLinked,
+ isIntegrated,
+ setTaggsNeedUpdate,
+ setSocialDataNeedUpdate,
+}) => {
const navigation = useNavigation();
+ const [modalVisible, setModalVisible] = useState(false);
+ const youMayPass = isLinked || isProfileView;
- return (
- <TouchableOpacity
- onPress={() =>
+ /*
+ case isProfileView:
+ case linked:
+ show normal ring, navigate to taggs view
+ case !linked:
+ don't show tagg
+ case !isProfileView:
+ case linked:
+ show normal ring, navigate to taggs view
+ case !linked:
+ show ring+, then...
+ case integrated_social:
+ show auth browser
+ case !integrated_social:
+ show modal
+ Tagg's "Tagg" will use the Ring instead of PurpleRing
+ */
+
+ const modalOrAuthBrowserOrPass = async () => {
+ if (youMayPass) {
+ if (INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1) {
navigation.navigate('SocialMediaTaggs', {
socialMediaType: social,
isProfileView: isProfileView,
- })
- }>
- <LinearGradient
- colors={[TAGGS_GRADIENT.start, TAGGS_GRADIENT.end]}
- useAngle={true}
- angle={154.72}
- angleCenter={{x: 0.5, y: 0.5}}
- style={[styles.gradient, style]}>
- <View style={styles.image} />
- </LinearGradient>
- </TouchableOpacity>
+ });
+ } else {
+ // TODO: we don't know what the link is...?
+ Linking.openURL(
+ `http://google.com/search?q=take+me+to+${social}+profile+page`,
+ );
+ }
+ } else {
+ if (isIntegrated) {
+ handlePressForAuthBrowser(social).then((success) => {
+ setTaggsNeedUpdate(success);
+ setSocialDataNeedUpdate(success ? [social] : []);
+ });
+ } else {
+ setModalVisible(true);
+ }
+ }
+ };
+
+ const pickTheRightRingHere = () => {
+ if (youMayPass) {
+ if (social === 'Tagg') {
+ return <Ring width={TAGG_ICON_DIM} height={TAGG_ICON_DIM} />;
+ } else {
+ return <PurpleRing width={TAGG_ICON_DIM} height={TAGG_ICON_DIM} />;
+ }
+ } else {
+ if (social === 'Tagg') {
+ return <RingPlus width={TAGG_ICON_DIM} height={TAGG_ICON_DIM} />;
+ } else {
+ return <PurpleRingPlus width={TAGG_ICON_DIM} height={TAGG_ICON_DIM} />;
+ }
+ }
+ };
+
+ const linkNonIntegratedSocial = async (username: string) => {
+ if (await registerNonIntegratedSocialLink(social, username)) {
+ Alert.alert(`Successfully linked ${social} 🎉`);
+ setTaggsNeedUpdate(true);
+ } else {
+ // If we display too fast the alert will get dismissed with the modal
+ setTimeout(() => {
+ Alert.alert(`Something went wrong, we can't link with ${social} 😔`);
+ }, 500);
+ }
+ };
+
+ return (
+ <>
+ {isProfileView && !isLinked ? (
+ <Fragment />
+ ) : (
+ <TouchableOpacity onPress={modalOrAuthBrowserOrPass}>
+ <SocialLinkModal
+ modalVisible={modalVisible}
+ setModalVisible={setModalVisible}
+ completionCallback={linkNonIntegratedSocial}
+ />
+ <View style={styles.container}>
+ <SocialIcon style={styles.image} social={social} />
+ {pickTheRightRingHere()}
+ </View>
+ </TouchableOpacity>
+ )}
+ </>
);
};
const styles = StyleSheet.create({
- gradient: {
- width: 80,
- height: 80,
- borderRadius: 40,
+ container: {
justifyContent: 'center',
alignItems: 'center',
+ marginHorizontal: 5,
},
image: {
- width: 72,
- height: 72,
- borderRadius: 37.5,
- backgroundColor: 'pink',
+ width: TAGG_ICON_DIM,
+ height: TAGG_ICON_DIM,
+ borderRadius: TAGG_ICON_DIM / 2,
+ position: 'absolute',
},
});
diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx
index 88f670b5..520cc266 100644
--- a/src/components/taggs/TaggsBar.tsx
+++ b/src/components/taggs/TaggsBar.tsx
@@ -1,10 +1,16 @@
// @refresh react
-import React from 'react';
+import React, {useEffect, useState} from 'react';
import {StyleSheet} from 'react-native';
import Animated from 'react-native-reanimated';
-import Tagg from './Tagg';
-import {PROFILE_CUTOUT_BOTTOM_Y} from '../../constants';
+import {
+ INTEGRATED_SOCIAL_LIST,
+ PROFILE_CUTOUT_BOTTOM_Y,
+ SOCIAL_LIST,
+} from '../../constants';
+import {AuthContext, ProfileContext} from '../../routes';
+import {getLinkedSocials} from '../../services';
import {StatusBarHeight} from '../../utils';
+import Tagg from './Tagg';
const {View, ScrollView, interpolate, Extrapolate} = Animated;
interface TaggsBarProps {
@@ -17,43 +23,59 @@ const TaggsBar: React.FC<TaggsBarProps> = ({
profileBodyHeight,
isProfileView,
}) => {
- const taggs: Array<JSX.Element> = [];
+ let [taggs, setTaggs] = useState<Object[]>([]);
+ let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true);
+ const context = isProfileView
+ ? React.useContext(ProfileContext)
+ : React.useContext(AuthContext);
+ const {user, socialsNeedUpdate} = context;
- taggs.push(
- <Tagg
- key={0}
- style={styles.tagg}
- social={'Instagram'}
- isProfileView={isProfileView}
- />,
- );
- taggs.push(
- <Tagg
- key={1}
- style={styles.tagg}
- social={'Facebook'}
- isProfileView={isProfileView}
- />,
- );
- taggs.push(
- <Tagg
- key={2}
- style={styles.tagg}
- social={'Twitter'}
- isProfileView={isProfileView}
- />,
- );
+ useEffect(() => {
+ const loadData = async () => {
+ getLinkedSocials(user.userId).then((linkedSocials) => {
+ const unlinkedSocials = SOCIAL_LIST.filter(
+ (s) => linkedSocials.indexOf(s) === -1,
+ );
+ let new_taggs = [];
+ let i = 0;
+ for (let social of linkedSocials) {
+ new_taggs.push(
+ <Tagg
+ key={i}
+ social={social}
+ isProfileView={isProfileView}
+ isLinked={true}
+ isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1}
+ setTaggsNeedUpdate={setTaggsNeedUpdate}
+ setSocialDataNeedUpdate={socialsNeedUpdate}
+ />,
+ );
+ i++;
+ }
+ for (let social of unlinkedSocials) {
+ new_taggs.push(
+ <Tagg
+ key={i}
+ social={social}
+ isProfileView={isProfileView}
+ isLinked={false}
+ isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1}
+ setTaggsNeedUpdate={setTaggsNeedUpdate}
+ setSocialDataNeedUpdate={socialsNeedUpdate}
+ />,
+ );
+ i++;
+ }
+ setTaggs(new_taggs);
+ setTaggsNeedUpdate(false);
+ });
+ };
+
+ if (taggsNeedUpdate) {
+ loadData();
+ }
+ }, [isProfileView, taggsNeedUpdate, user.userId]);
- for (let i = 3; i < 10; i++) {
- taggs.push(
- <Tagg
- key={i}
- style={styles.tagg}
- social={'Instagram'}
- isProfileView={isProfileView}
- />,
- );
- }
const shadowOpacity: Animated.Node<number> = interpolate(y, {
inputRange: [
PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight,
@@ -105,9 +127,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
paddingHorizontal: 15,
},
- tagg: {
- marginHorizontal: 14,
- },
});
export default TaggsBar;