Profile.tsx 13 KB

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