Profile.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import React, { FC, useState } from 'react';
  2. import { Text, TouchableOpacity, View } from 'react-native';
  3. import { Image } from 'expo-image';
  4. import { useNavigation } from '@react-navigation/native';
  5. import * as FileSystem from 'expo-file-system';
  6. import { ProfileStyles, ScoreStyles, TBTStyles } from './styles';
  7. import { storage, StoreType } from 'src/storage';
  8. import { AvatarWithInitials, WarningModal } from 'src/components';
  9. import { API_HOST } from '../../../../constants';
  10. import { getFontSize } from '../../../../utils';
  11. import { adaptiveStyle, Colors } from '../../../../theme';
  12. import TickIcon from '../../../../../assets/icons/tick.svg';
  13. import UNIcon from '../../../../../assets/icons/un_icon.svg';
  14. import NMIcon from '../../../../../assets/icons/nm_icon.svg';
  15. import TBTIcon from '../../../../../assets/icons/tbt.svg';
  16. import { NAVIGATION_PAGES } from '../../../../types';
  17. import { useConnection } from 'src/contexts/ConnectionContext';
  18. import Tooltip from 'react-native-walkthrough-tooltip';
  19. type Props = {
  20. avatar: string | null;
  21. first_name: string;
  22. last_name: string;
  23. date_of_birth: number;
  24. homebase_flag: string;
  25. homebase2_flag: string | null;
  26. index: number;
  27. score: any[];
  28. active_score: number;
  29. tbt_score?: number;
  30. tbt_rank?: number;
  31. badge_tbt?: number;
  32. badge_1281: number;
  33. badge_un: number;
  34. auth: number;
  35. userId: number;
  36. };
  37. //TODO: Profile
  38. export const Profile: FC<Props> = ({
  39. avatar,
  40. first_name,
  41. last_name,
  42. date_of_birth,
  43. homebase_flag,
  44. homebase2_flag,
  45. index,
  46. score,
  47. active_score,
  48. tbt_rank,
  49. badge_tbt,
  50. badge_1281,
  51. badge_un,
  52. auth,
  53. userId
  54. }) => {
  55. const navigation = useNavigation();
  56. const scoreNames = ['NM', 'DARE', 'UN', 'UN+', 'TCC', 'DEEP', 'YES', 'SLOW', 'WHS', 'KYE'];
  57. const netInfo = useConnection();
  58. const token = storage.get('token', StoreType.STRING);
  59. const [modalType, setModalType] = useState<string | null>(null);
  60. const [toolTipVisible, setToolTipVisible] = useState<number | string | null>(null);
  61. const handlePress = () => {
  62. if (!netInfo?.isInternetReachable) {
  63. setModalType('offline');
  64. } else if (!token) {
  65. setModalType('unauthorized');
  66. } else {
  67. navigation.navigate(...([NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW, { userId }] as never));
  68. }
  69. };
  70. const avatarBaseUri = netInfo?.isInternetReachable
  71. ? `${API_HOST}/img/avatars/`
  72. : `${FileSystem.documentDirectory}avatars/`;
  73. const flagBaseUri = netInfo?.isInternetReachable
  74. ? `${API_HOST}/img/flags_new/`
  75. : `${FileSystem.documentDirectory}flags/`;
  76. const TBRanking = () => {
  77. const colors = [
  78. 'rgba(237, 147, 52, 1)',
  79. 'rgba(128, 128, 128, 1)',
  80. 'rgba(211, 211, 211, 1)',
  81. 'rgba(187, 95, 5, 1)',
  82. '#808080'
  83. ];
  84. const Rank = ({ color }: { color: string }) => (
  85. <View style={adaptiveStyle([ProfileStyles.badge, { backgroundColor: color }], {})}>
  86. <TBTIcon />
  87. </View>
  88. );
  89. return (
  90. <Tooltip
  91. isVisible={toolTipVisible === 'TBT'}
  92. content={<Text style={{ color: Colors.DARK_BLUE }}>{handleGetTooltip('TBT')}</Text>}
  93. contentStyle={{ backgroundColor: Colors.WHITE }}
  94. placement="top"
  95. onClose={() => setToolTipVisible(null)}
  96. backgroundColor="transparent"
  97. allowChildInteraction={false}
  98. parentWrapperStyle={adaptiveStyle(TBTStyles.badgeRoot, {})}
  99. >
  100. <TouchableOpacity
  101. style={{ flex: 1 }}
  102. disabled={!tbt_rank || tbt_rank < 1}
  103. onPress={() => setToolTipVisible('TBT')}
  104. >
  105. <View style={adaptiveStyle(TBTStyles.badgeWrapper, {})}>
  106. {badge_tbt && tbt_rank ? (
  107. <Rank color={colors[tbt_rank - 1]} />
  108. ) : (
  109. <View style={adaptiveStyle(ProfileStyles.badge, {})} />
  110. )}
  111. {tbt_rank && tbt_rank >= 1 ? (
  112. <Text style={adaptiveStyle([ScoreStyles.scoreNameText], {})}>TBT # {tbt_rank}</Text>
  113. ) : (
  114. <View style={{ height: 11 }} />
  115. )}
  116. </View>
  117. </TouchableOpacity>
  118. </Tooltip>
  119. );
  120. };
  121. const EmptyScore: FC<{ scoreName: string; index: number }> = ({ scoreName, index }) => {
  122. return (
  123. <Tooltip
  124. isVisible={toolTipVisible === index}
  125. content={<Text style={{ color: Colors.DARK_BLUE }}>{handleGetTooltip(scoreName)}</Text>}
  126. contentStyle={{ backgroundColor: Colors.WHITE }}
  127. placement="top"
  128. onClose={() => setToolTipVisible(null)}
  129. backgroundColor="transparent"
  130. allowChildInteraction={false}
  131. parentWrapperStyle={adaptiveStyle(ScoreStyles.scoreWrapper, {})}
  132. >
  133. <TouchableOpacity onPress={() => setToolTipVisible(index)}>
  134. <Text style={adaptiveStyle([ScoreStyles.scoreHeaderText], {})}>-</Text>
  135. <Text style={adaptiveStyle([ScoreStyles.scoreNameText], {})}>{scoreName}</Text>
  136. </TouchableOpacity>
  137. </Tooltip>
  138. );
  139. };
  140. const handleGetTooltip = (score: string) => {
  141. switch (score) {
  142. case 'NM':
  143. return 'NM list of regions';
  144. case 'DARE':
  145. return (
  146. <Text>
  147. <Text style={{ fontWeight: '700' }}>D</Text>istinctive{' '}
  148. <Text style={{ fontWeight: '700' }}>A</Text>lternative{' '}
  149. <Text style={{ fontWeight: '700' }}>R</Text>emote{' '}
  150. <Text style={{ fontWeight: '700' }}>E</Text>
  151. xtreme places
  152. </Text>
  153. );
  154. case 'UN':
  155. return 'UN countries';
  156. case 'UN+':
  157. return 'UN countries and territories';
  158. case 'TCC':
  159. return (
  160. <Text>
  161. <Text style={{ fontStyle: 'italic' }}>Travelers’ Century Club</Text> list
  162. </Text>
  163. );
  164. case 'DEEP':
  165. return (
  166. <Text>
  167. <Text style={{ fontWeight: '700' }}>D</Text>efinite{' '}
  168. <Text style={{ fontWeight: '700' }}>E</Text>xhaustive{'\n'}
  169. <Text style={{ fontWeight: '700' }}>E</Text>xploring{' '}
  170. <Text style={{ fontWeight: '700' }}>P</Text>
  171. roportion{'\n'}NM regions to UN countries ratio{'\n'}
  172. <Text style={{ fontSize: 11 }}>
  173. For travellers with minimum 30 UN countries visited.
  174. </Text>
  175. </Text>
  176. );
  177. case 'YES':
  178. return 'Years Elapsed Since list';
  179. case 'SLOW':
  180. return 'SLOW travel list';
  181. case 'WHS':
  182. return 'World Heritage Sites';
  183. case 'KYE':
  184. return 'Know Your Earth geographic based list';
  185. case 'TBT':
  186. return 'The Biggest Travellers based on all rankings';
  187. }
  188. return score;
  189. };
  190. return (
  191. <View style={ProfileStyles.wrapper}>
  192. <View
  193. style={[
  194. ProfileStyles.index,
  195. {
  196. width: index + 1 < 100 ? 26 : 38
  197. }
  198. ]}
  199. >
  200. <Text style={adaptiveStyle(ScoreStyles.rankText, {})}>{index + 1}</Text>
  201. </View>
  202. <View style={{ flex: 1, paddingLeft: 8, paddingRight: 3, paddingBottom: 3 }}>
  203. <TouchableOpacity onPress={() => handlePress()}>
  204. <View style={adaptiveStyle(ProfileStyles.profileRoot, {})}>
  205. <View style={ProfileStyles.avatarContainer}>
  206. {avatar ? (
  207. <Image
  208. style={adaptiveStyle(ProfileStyles.profileAvatar, {})}
  209. source={{ uri: avatarBaseUri + avatar }}
  210. />
  211. ) : homebase_flag ? (
  212. <AvatarWithInitials
  213. text={`${first_name[0] ?? ''}${last_name[0] ?? ''}`}
  214. flag={flagBaseUri + homebase_flag}
  215. size={48}
  216. />
  217. ) : null}
  218. </View>
  219. <View style={adaptiveStyle(ProfileStyles.profileDataRoot, {})}>
  220. <Text
  221. style={adaptiveStyle(
  222. [ProfileStyles.profileFirstLastName, { fontSize: getFontSize(14), flex: 0 }],
  223. {}
  224. )}
  225. >
  226. {first_name ?? ''} {last_name ?? ''}
  227. </Text>
  228. <View style={adaptiveStyle(ProfileStyles.profileDataContainer, {})}>
  229. <View style={adaptiveStyle(ProfileStyles.profileDataWrapper, {})}>
  230. <Text style={adaptiveStyle(ProfileStyles.profileAge, {})}>
  231. Age: {date_of_birth ?? ''}
  232. </Text>
  233. {homebase_flag && (
  234. <Image
  235. source={{ uri: flagBaseUri + homebase_flag }}
  236. style={adaptiveStyle(ProfileStyles.countryFlag, {})}
  237. />
  238. )}
  239. {homebase2_flag && homebase2_flag !== homebase_flag ? (
  240. <Image
  241. source={{ uri: flagBaseUri + homebase2_flag }}
  242. style={adaptiveStyle([ProfileStyles.countryFlag, { marginLeft: -15 }], {})}
  243. />
  244. ) : null}
  245. <View style={adaptiveStyle(ProfileStyles.badgesWrapper, {})}>
  246. {auth ? <TickIcon /> : null}
  247. {badge_un ? <UNIcon /> : null}
  248. {badge_1281 ? <NMIcon /> : null}
  249. </View>
  250. </View>
  251. </View>
  252. </View>
  253. </View>
  254. </TouchableOpacity>
  255. <View style={adaptiveStyle(ScoreStyles.rankingWrapper, {})}>
  256. <View style={adaptiveStyle(ScoreStyles.nmWrapper, {})}>
  257. <View style={ScoreStyles.score}>
  258. <Text style={adaptiveStyle(ScoreStyles.activeScoreRanking, {})}>
  259. {score[active_score]}
  260. </Text>
  261. <Text style={adaptiveStyle(ScoreStyles.activeScoreName, {})}>
  262. {scoreNames[active_score]}
  263. </Text>
  264. </View>
  265. <TBRanking />
  266. </View>
  267. <View style={adaptiveStyle(ScoreStyles.rankingScoresWrapper, {})}>
  268. {score.map((number, index) => {
  269. if (scoreNames[index] === 'SLOW' && number >= 4500)
  270. return <EmptyScore key={index} scoreName={scoreNames[index]} index={index} />;
  271. if (scoreNames[index] === 'YES' && number >= 10000)
  272. return <EmptyScore key={index} scoreName={scoreNames[index]} index={index} />;
  273. if (!number)
  274. return <EmptyScore key={index} scoreName={scoreNames[index]} index={index} />;
  275. return (
  276. <Tooltip
  277. isVisible={toolTipVisible === index}
  278. content={
  279. <Text style={{ color: Colors.DARK_BLUE }}>
  280. {handleGetTooltip(scoreNames[index])}
  281. </Text>
  282. }
  283. contentStyle={{ backgroundColor: Colors.WHITE }}
  284. placement="top"
  285. onClose={() => setToolTipVisible(null)}
  286. backgroundColor="transparent"
  287. allowChildInteraction={false}
  288. key={index}
  289. parentWrapperStyle={adaptiveStyle(ScoreStyles.scoreWrapper, {})}
  290. >
  291. <TouchableOpacity onPress={() => setToolTipVisible(index)}>
  292. <Text style={adaptiveStyle([ScoreStyles.scoreHeaderText], {})}>{number}</Text>
  293. <Text style={adaptiveStyle([ScoreStyles.scoreNameText], {})}>
  294. {scoreNames[index]}
  295. </Text>
  296. </TouchableOpacity>
  297. </Tooltip>
  298. );
  299. })}
  300. </View>
  301. </View>
  302. </View>
  303. {modalType && (
  304. <WarningModal type={modalType} isVisible={true} onClose={() => setModalType(null)} />
  305. )}
  306. </View>
  307. );
  308. };