index.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import React, { FC, useCallback, useEffect, useState } from 'react';
  2. import { Linking, ScrollView, Text, TouchableOpacity, View, Image, Platform } from 'react-native';
  3. import { CommonActions, NavigationProp, useFocusEffect } from '@react-navigation/native';
  4. import ReactModal from 'react-native-modal';
  5. import { usePostGetProfileInfoDataQuery, usePostGetProfileUpdatesQuery } from '@api/user';
  6. import {
  7. PageWrapper,
  8. Loading,
  9. AvatarWithInitials,
  10. Header,
  11. WarningModal
  12. } from '../../../components';
  13. import { adaptiveStyle, Colors } from '../../../theme';
  14. import { styles } from './styles';
  15. import { API_HOST } from '../../../constants';
  16. import { NAVIGATION_PAGES } from '../../../types';
  17. import { storage, StoreType } from '../../../storage';
  18. import { getFontSize } from '../../../utils';
  19. import IconFacebook from '../../../../assets/icons/facebook.svg';
  20. import IconInstagram from '../../../../assets/icons/instagram.svg';
  21. import IconTwitter from '../../../../assets/icons/x(twitter).svg';
  22. import IconYouTube from '../../../../assets/icons/youtube.svg';
  23. import IconGlobe from '../../../../assets/icons/bottom-navigation/globe.svg';
  24. import IconLink from '../../../../assets/icons/link.svg';
  25. import GearIcon from '../../../../assets/icons/gear.svg';
  26. import TBTIcon from '../../../../assets/icons/tbt.svg';
  27. import TickIcon from '../../../../assets/icons/tick.svg';
  28. import UNIcon from '../../../../assets/icons/un_icon.svg';
  29. import NMIcon from '../../../../assets/icons/nm_icon.svg';
  30. import UN50Icon from '../../../../assets/icons/un-50.svg';
  31. import UN100Icon from '../../../../assets/icons/un-100.svg';
  32. import UN150Icon from '../../../../assets/icons/un-150.svg';
  33. import ChevronIcon from '../../../../assets/icons/chevron-left.svg';
  34. import ShareIcon from '../../../../assets/icons/share.svg';
  35. import UnverifiedIcon from '../../../../assets/icons/unverified.svg';
  36. import { ProfileStyles, ScoreStyles, TBTStyles } from '../TravellersScreen/Components/styles';
  37. import UnauthenticatedProfileScreen from './UnauthenticatedProfileScreen';
  38. import { PersonalInfo } from './Components/PersonalInfo';
  39. import { usePostUpdateFriendStatusMutation } from '@api/friends';
  40. import Tooltip from 'react-native-walkthrough-tooltip';
  41. type Props = {
  42. navigation: NavigationProp<any>;
  43. route: any;
  44. };
  45. const ProfileScreen: FC<Props> = ({ navigation, route }) => {
  46. const isPublicView = route.name === NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW;
  47. const token = storage.get('token', StoreType.STRING) as string;
  48. const currentUserId = storage.get('uid', StoreType.STRING) as string;
  49. if (!token) return <UnauthenticatedProfileScreen />;
  50. const { data: userData, isFetching } = usePostGetProfileInfoDataQuery(
  51. token,
  52. isPublicView ? route.params?.userId : +currentUserId,
  53. true
  54. );
  55. const { data: lastUpdates } = usePostGetProfileUpdatesQuery(
  56. token,
  57. isPublicView ? route.params?.userId : +currentUserId,
  58. true
  59. );
  60. const { mutateAsync: updateFriendStatus } = usePostUpdateFriendStatusMutation();
  61. const [isFriend, setIsFriend] = React.useState<0 | 1>(0);
  62. const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState(false);
  63. const [modalState, setModalState] = useState({
  64. isModalVisible: false,
  65. isWarningVisible: false
  66. });
  67. const [tooltipVisible, setTooltipVisible] = useState(false);
  68. useFocusEffect(
  69. useCallback(() => {
  70. if (!route.params?.hideTabBar) {
  71. navigation.getParent()?.setOptions({
  72. tabBarStyle: {
  73. display: 'flex',
  74. ...Platform.select({
  75. android: {
  76. height: 58
  77. }
  78. })
  79. }
  80. });
  81. }
  82. }, [navigation])
  83. );
  84. useEffect(() => {
  85. setIsFriend(userData?.data?.is_friend ?? 0);
  86. if (userData && userData?.data?.own_profile === 1) {
  87. const userInfo = {
  88. avatar: userData?.data?.user_data.avatar ?? '',
  89. first_name: userData?.data?.user_data.first_name,
  90. last_name: userData?.data?.user_data.last_name,
  91. homebase_flag: userData?.data?.user_data.flag1
  92. };
  93. storage.set('currentUserData', JSON.stringify(userInfo));
  94. }
  95. }, [userData]);
  96. if (!userData?.data || !lastUpdates || isFetching) return <Loading />;
  97. const data = userData.data;
  98. const links = JSON.parse(data.user_data.links_json);
  99. const handleGoToMap = () => {
  100. data.own_profile === 0
  101. ? navigation.navigate(NAVIGATION_PAGES.USERS_MAP, { userId: route.params?.userId, data })
  102. : navigation.dispatch(
  103. CommonActions.reset({
  104. index: 1,
  105. routes: [{ name: NAVIGATION_PAGES.IN_APP_MAP_TAB }]
  106. })
  107. );
  108. };
  109. const closeModal = (modalName: string) => {
  110. setModalState((prevState) => ({ ...prevState, [modalName]: false }));
  111. };
  112. const openModal = (modalName: string) => {
  113. setModalState((prevState) => ({ ...prevState, [modalName]: true }));
  114. };
  115. const TBRanking = () => {
  116. const colors = [
  117. 'rgba(237, 147, 52, 1)',
  118. 'rgba(128, 128, 128, 1)',
  119. 'rgba(211, 211, 211, 1)',
  120. 'rgba(187, 95, 5, 1)',
  121. '#808080'
  122. ];
  123. const Rank = ({ color }: { color: string }) => (
  124. <View style={adaptiveStyle([ProfileStyles.badge, { backgroundColor: color }], {})}>
  125. <TBTIcon />
  126. </View>
  127. );
  128. return (
  129. <TouchableOpacity
  130. style={adaptiveStyle([TBTStyles.badgeRoot, styles.badgeRoot], {})}
  131. disabled={!data.scores.rank_tbt || data.scores.rank_tbt < 1}
  132. >
  133. <View style={adaptiveStyle([TBTStyles.badgeWrapper, { gap: 10 }], {})}>
  134. {data.user_data.badge_tbt && data.scores.rank_tbt ? (
  135. <Rank color={colors[data.scores.rank_tbt - 1]} />
  136. ) : null}
  137. {data.scores.rank_tbt && data.scores.rank_tbt >= 1 ? (
  138. <Text style={adaptiveStyle([ScoreStyles.scoreNameText], {})}>
  139. TBT # {data.scores.rank_tbt}
  140. </Text>
  141. ) : (
  142. <View style={{ height: 11 }} />
  143. )}
  144. </View>
  145. </TouchableOpacity>
  146. );
  147. };
  148. const handleOpenUrl = (url: string | undefined) => {
  149. url && Linking.openURL(url);
  150. };
  151. const hasActiveLinks = () => {
  152. return (
  153. (links?.f?.link && links?.f?.active !== 0) ||
  154. (links?.i?.link && links?.i?.active !== 0) ||
  155. (links?.t?.link && links?.t?.active !== 0) ||
  156. (links?.y?.link && links?.y?.active !== 0) ||
  157. (links?.www?.link && links?.www?.active !== 0) ||
  158. (links?.other?.link && links?.other?.active !== 0)
  159. );
  160. };
  161. const handleUpdateFriendStatus = async () => {
  162. await updateFriendStatus(
  163. { token: token as string, id: data.friend_db_id, status: -1 },
  164. {
  165. onSuccess: () => {
  166. setIsFriend(0);
  167. }
  168. }
  169. );
  170. };
  171. return (
  172. <PageWrapper>
  173. <Header label="Profile" />
  174. <ScrollView
  175. showsVerticalScrollIndicator={false}
  176. contentContainerStyle={{ paddingBottom: 58 }}
  177. >
  178. <TouchableOpacity
  179. style={[styles.usersMap, { backgroundColor: '#EBF2F5' }]}
  180. onPress={handleGoToMap}
  181. >
  182. <Image
  183. source={{
  184. uri: `${API_HOST}/img/single_maps/${isPublicView ? route.params?.userId : currentUserId}.png`
  185. }}
  186. style={styles.usersMap}
  187. />
  188. </TouchableOpacity>
  189. <View style={styles.pageWrapper}>
  190. <View style={{ gap: 8 }}>
  191. {data.user_data.avatar ? (
  192. <Image
  193. style={styles.avatar}
  194. source={{ uri: API_HOST + '/img/avatars/' + data.user_data.avatar }}
  195. />
  196. ) : (
  197. <AvatarWithInitials
  198. text={`${data.user_data.first_name[0] ?? ''}${data.user_data.last_name[0] ?? ''}`}
  199. flag={API_HOST + '/img/flags_new/' + data.user_data.flag1}
  200. size={64}
  201. borderColor={Colors.WHITE}
  202. />
  203. )}
  204. {data.scores.rank_tbt && data.scores.rank_tbt >= 1 ? <TBRanking /> : null}
  205. {isFriend === 1 && token && data.own_profile === 0 ? (
  206. <TouchableOpacity style={styles.friend} onPress={() => openModal('isModalVisible')}>
  207. <Text style={styles.friendText}>Friend</Text>
  208. <View style={{ transform: 'rotate(180deg)' }}>
  209. <ChevronIcon fill={Colors.WHITE} height={8} />
  210. </View>
  211. </TouchableOpacity>
  212. ) : null}
  213. </View>
  214. <View style={{ gap: 5, flex: 1 }}>
  215. <View style={{ height: 34 }}></View>
  216. <View style={styles.nameRow}>
  217. <Text style={[styles.headerText, { fontSize: getFontSize(18) }]}>
  218. {data.user_data.first_name} {data.user_data.last_name}
  219. </Text>
  220. </View>
  221. <View style={styles.userInfoContainer}>
  222. <View style={styles.userInfo}>
  223. <Text style={styles.ageText}>Age: {data.user_data.age}</Text>
  224. <Image
  225. source={{ uri: API_HOST + '/img/flags_new/' + data.user_data.flag1 }}
  226. style={styles.countryFlag}
  227. />
  228. {data.user_data.flag2 && data.user_data.flag2 !== data.user_data.flag1 ? (
  229. <Image
  230. source={{ uri: API_HOST + '/img/flags_new/' + data.user_data.flag2 }}
  231. style={[styles.countryFlag, { marginLeft: -15 }]}
  232. />
  233. ) : null}
  234. <View style={adaptiveStyle(ProfileStyles.badgesWrapper, {})}>
  235. {data.user_data.auth ? <TickIcon /> : null}
  236. {data.user_data.badge_un ? <UNIcon /> : null}
  237. {data.user_data.badge_nm ? <NMIcon /> : null}
  238. {data.user_data.badge_un_150 ? <UN150Icon /> : null}
  239. {data.user_data.badge_un_100 ? <UN100Icon /> : null}
  240. {data.user_data.badge_un_50 ? <UN50Icon /> : null}
  241. {data.user_data.badge_ghost ? (
  242. <Tooltip
  243. isVisible={tooltipVisible}
  244. onClose={() => setTooltipVisible(false)}
  245. content={<Text style={{ color: Colors.DARK_BLUE }}>Unverified User</Text>}
  246. contentStyle={{ backgroundColor: Colors.WHITE }}
  247. backgroundColor="transparent"
  248. allowChildInteraction={false}
  249. placement="top"
  250. >
  251. <TouchableOpacity onPress={() => setTooltipVisible(true)}>
  252. <UnverifiedIcon />
  253. </TouchableOpacity>
  254. </Tooltip>
  255. ) : null}
  256. </View>
  257. </View>
  258. {data.own_profile === 1 ? (
  259. <>
  260. <TouchableOpacity
  261. style={[styles.settings, { right: 25 }]}
  262. onPress={() =>
  263. navigation.navigate(NAVIGATION_PAGES.SHARE_PROFILE, {
  264. data: {
  265. avatar: data.user_data.avatar,
  266. first_name: data.user_data.first_name,
  267. last_name: data.user_data.last_name,
  268. flag1: data.user_data.flag1,
  269. flag2: data.user_data.flag2,
  270. id: +currentUserId,
  271. auth: data.user_data.auth,
  272. badge_un: data.user_data.badge_un,
  273. badge_nm: data.user_data.badge_nm,
  274. badge_un_50: data.user_data.badge_un_50,
  275. badge_un_100: data.user_data.badge_un_100,
  276. badge_un_150: data.user_data.badge_un_150,
  277. scores: data.scores
  278. }
  279. })
  280. }
  281. >
  282. <ShareIcon
  283. width={20}
  284. height={20}
  285. fill={Colors.DARK_BLUE}
  286. style={{ alignSelf: 'center' }}
  287. />
  288. </TouchableOpacity>
  289. <TouchableOpacity
  290. style={styles.settings}
  291. onPress={() => navigation.navigate(NAVIGATION_PAGES.EDIT_PERSONAL_INFO)}
  292. >
  293. <GearIcon
  294. width={20}
  295. height={20}
  296. fill={Colors.DARK_BLUE}
  297. style={{ alignSelf: 'center' }}
  298. />
  299. </TouchableOpacity>
  300. </>
  301. ) : null}
  302. </View>
  303. {hasActiveLinks() && (
  304. <View style={styles.linksBox}>
  305. {links?.f?.link && links?.f?.active !== 0 ? (
  306. <TouchableOpacity onPress={() => handleOpenUrl(links?.f?.link)}>
  307. <IconFacebook fill={Colors.DARK_BLUE} height={16} />
  308. </TouchableOpacity>
  309. ) : null}
  310. {links?.i?.link && links?.i?.active !== 0 ? (
  311. <TouchableOpacity onPress={() => handleOpenUrl(links?.i?.link)}>
  312. <IconInstagram fill={Colors.DARK_BLUE} height={16} />
  313. </TouchableOpacity>
  314. ) : null}
  315. {links?.t?.link && links?.t?.active !== 0 ? (
  316. <TouchableOpacity onPress={() => handleOpenUrl(links?.t?.link)}>
  317. <IconTwitter fill={Colors.DARK_BLUE} height={16} />
  318. </TouchableOpacity>
  319. ) : null}
  320. {links?.y?.link && links?.y?.active !== 0 ? (
  321. <TouchableOpacity onPress={() => handleOpenUrl(links?.y?.link)}>
  322. <IconYouTube fill={Colors.DARK_BLUE} height={16} />
  323. </TouchableOpacity>
  324. ) : null}
  325. {links?.www?.link && links?.www?.active !== 0 ? (
  326. <TouchableOpacity onPress={() => handleOpenUrl(links?.www?.link)}>
  327. <IconGlobe fill={Colors.DARK_BLUE} height={16} />
  328. </TouchableOpacity>
  329. ) : null}
  330. {links?.other?.link && links?.other?.active !== 0 ? (
  331. <TouchableOpacity onPress={() => handleOpenUrl(links?.other?.link)}>
  332. <IconLink fill={Colors.DARK_BLUE} height={16} />
  333. </TouchableOpacity>
  334. ) : null}
  335. </View>
  336. )}
  337. </View>
  338. </View>
  339. <PersonalInfo
  340. data={{
  341. bio: data.user_data.bio,
  342. scores: data.scores,
  343. homebase: data.user_data.homeregion,
  344. series: data.series,
  345. friends: data.friends,
  346. firstName: data.user_data.first_name,
  347. lastName: data.user_data.last_name,
  348. friendRequestSent: data.friend_request_sent,
  349. friendRequestReceived: data.friend_request_received,
  350. isFriend,
  351. friendDbId: data.friend_db_id,
  352. ownProfile: data.own_profile
  353. }}
  354. updates={lastUpdates?.data ? lastUpdates.data?.updates : null}
  355. userId={isPublicView ? route.params?.userId : +currentUserId}
  356. navigation={navigation}
  357. isPublicView={isPublicView}
  358. token={token ? token : null}
  359. />
  360. </ScrollView>
  361. <ReactModal
  362. isVisible={modalState.isModalVisible}
  363. onBackdropPress={() => closeModal('isModalVisible')}
  364. style={styles.modal}
  365. statusBarTranslucent={true}
  366. presentationStyle="overFullScreen"
  367. onModalHide={() => {
  368. if (shouldOpenWarningModal) {
  369. openModal('isWarningVisible');
  370. setShouldOpenWarningModal(false);
  371. }
  372. }}
  373. >
  374. <View style={styles.wrapper}>
  375. <TouchableOpacity
  376. style={styles.btnModalEdit}
  377. onPress={() => {
  378. closeModal('isModalVisible');
  379. setShouldOpenWarningModal(true);
  380. }}
  381. >
  382. <Text style={styles.btnModalEditText}>Unfriend</Text>
  383. <View style={{ transform: 'rotate(180deg)' }}>
  384. <ChevronIcon fill={Colors.DARK_BLUE} height={11} />
  385. </View>
  386. </TouchableOpacity>
  387. </View>
  388. </ReactModal>
  389. <WarningModal
  390. type={'confirm'}
  391. isVisible={modalState.isWarningVisible}
  392. message={`Are you sure you want to unfriend ${data.user_data.first_name} ${data.user_data.last_name}?`}
  393. action={handleUpdateFriendStatus}
  394. onClose={() => closeModal('isWarningVisible')}
  395. title=""
  396. />
  397. </PageWrapper>
  398. );
  399. };
  400. export default ProfileScreen;