From 9da19cdcb6c7596d60afde6d0d559f06a24a0627 Mon Sep 17 00:00:00 2001 From: George Rusu <56009869+grusu6928@users.noreply.github.com> Date: Sun, 25 Oct 2020 15:21:38 -0700 Subject: [TMA-237] Added modal for user registration and redirect (#61) * move async-storage * removed lock files * added lock files to gitignore * added the wrong file to gitignore * added modal for user registration and redirect * api call to get list of linked socials for each user to display appropriate icon * fixed indentation and linting * refactored modal and browser sign-in * now dynamically adding linked and unlinked taggs, added a bunch of TODOs for tomorrow * added svg icons * done? finished taggs bar UI and all the navigations including modal * fixed some bugs and added more TODOs * fixed some bugs and refactored posts fetching logic * fixed taggs bar bug * done, it will update everything correctly * added comments * added tiktok Co-authored-by: hsalhab Co-authored-by: george Co-authored-by: Ivan Chen --- src/components/common/SocialIcon.tsx | 3 + src/components/common/SocialLinkModal.tsx | 118 +++++++++++++++++++ src/components/common/index.ts | 1 + src/components/onboarding/SocialMediaLinker.tsx | 112 ++---------------- src/components/taggs/Tagg.tsx | 146 +++++++++++++++++++----- src/components/taggs/TaggsBar.tsx | 101 +++++++++------- 6 files changed, 307 insertions(+), 174 deletions(-) create mode 100644 src/components/common/SocialLinkModal.tsx (limited to 'src/components') 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 = ({ 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 = ({ + modalVisible, + setModalVisible, + completionCallback, +}) => { + const [username, setUsername] = React.useState(''); + return ( + <> + + {}}> + + + + {/* link button */} + { + setModalVisible(!modalVisible); + setUsername(''); + completionCallback(username); + }}> + Link + + {/* cancel button */} + { + setUsername(''); + setModalVisible(!modalVisible); + }} + style={styles.cancelStyle}> + Cancel + + + + + + + ); +}; + +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 = ({ 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 = 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 = ({ style={styles.container}> {label} - {state.authenticated && ( + {state.socialLinked && ( void; + setSocialDataNeedUpdate: (_: string[]) => void; } -const Tagg: React.FC = ({style, social, isProfileView}) => { +const Tagg: React.FC = ({ + social, + isProfileView, + isLinked, + isIntegrated, + setTaggsNeedUpdate, + setSocialDataNeedUpdate, +}) => { const navigation = useNavigation(); + const [modalVisible, setModalVisible] = useState(false); + const youMayPass = isLinked || isProfileView; - return ( - + /* + 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, - }) - }> - - - - + }); + } 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 ; + } else { + return ; + } + } else { + if (social === 'Tagg') { + return ; + } else { + return ; + } + } + }; + + 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 ? ( + + ) : ( + + + + + {pickTheRightRingHere()} + + + )} + ); }; 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 = ({ profileBodyHeight, isProfileView, }) => { - const taggs: Array = []; + let [taggs, setTaggs] = useState([]); + let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true); + const context = isProfileView + ? React.useContext(ProfileContext) + : React.useContext(AuthContext); + const {user, socialsNeedUpdate} = context; - taggs.push( - , - ); - taggs.push( - , - ); - taggs.push( - , - ); + 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( + , + ); + i++; + } + for (let social of unlinkedSocials) { + new_taggs.push( + , + ); + i++; + } + setTaggs(new_taggs); + setTaggsNeedUpdate(false); + }); + }; + + if (taggsNeedUpdate) { + loadData(); + } + }, [isProfileView, taggsNeedUpdate, user.userId]); - for (let i = 3; i < 10; i++) { - taggs.push( - , - ); - } const shadowOpacity: Animated.Node = 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; -- cgit v1.2.3-70-g09d2