Explorar el Código

Merge branch 'ranking-list' of SashaGoncharov19/nomadmania-app into dev

Viktoriia hace 1 año
padre
commit
7a8474b81b

+ 1 - 0
src/modules/api/user/queries/index.ts

@@ -2,3 +2,4 @@ export * from './use-post-get-profile';
 export * from './use-post-set-profile';
 export * from './use-post-get-profile-info';
 export * from './use-post-get-profile-info-public';
+export * from './use-post-get-profile-regions';

+ 16 - 0
src/modules/api/user/queries/use-post-get-profile-regions.tsx

@@ -0,0 +1,16 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { userQueryKeys } from '../user-query-keys';
+import { type PostGetProfileRegionsReturn, userApi } from '../user-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostGetProfileRegions = (uid: number, type: string) => {
+  return useQuery<PostGetProfileRegionsReturn, BaseAxiosError>({
+    queryKey: userQueryKeys.getProfileRegions(uid, type),
+    queryFn: async () => {
+      const response = await userApi.getProfileRegions(uid, type);
+      return response.data;
+    }
+  });
+};

+ 96 - 1
src/modules/api/user/user-api.tsx

@@ -97,6 +97,99 @@ export type Score = {
   score: number;
 };
 
+export interface PostGetProfileRegionsReturn extends ResponseType {
+  data: NM | MQP | UN_UNP_TCC | YES | SLOW | WHS;
+}
+
+export type NM = {
+  all_count: number;
+  firsts: Record<string, number | null>;
+  last: Record<string, number | null>;
+  megaregions: {
+    id: number;
+    name: string;
+    regions: {
+      id: number;
+      name: string;
+      flag1: string;
+      flag2: string | null;
+    }[];
+    transits: number[];
+    visits: number[];
+  }[];
+  transits_count: number;
+  visits_count: number;
+};
+
+export type MQP = {
+  all_count: number;
+  megaregions: {
+    id: number;
+    name: string;
+    regions: {
+      id: number;
+      name: string;
+      flag1: string;
+      flag2: string | null;
+    }[];
+    transits: number[];
+    visits: number[];
+  }[];
+  transits_count: number;
+  visits_count: number;
+};
+
+export type UN_UNP_TCC = [
+  {
+    name: string;
+    name_en: string;
+    flag1: string;
+    flag2: string | null;
+  }[],
+  string[],
+  number,
+  number
+];
+
+export type YES = [
+  {
+    id: number;
+    name: string;
+    name_en: string;
+    flag1: string;
+    flag2: string | null;
+  }[],
+  Record<
+    string,
+    {
+      year: number;
+      score: number;
+      country: string;
+    }
+  >
+];
+
+export type SLOW = [
+  {
+    country_id: number;
+    country: string;
+    flag: string;
+    mega: number[];
+    visited: 0 | 1;
+    slow11: 0 | 1;
+    slow31: 0 | 1;
+    slow101: 0 | 1;
+    yes: number;
+  }[],
+  Record<string, string>
+];
+
+export type WHS = {
+  name: string;
+  flag: string;
+  visited: boolean;
+}[];
+
 export const userApi = {
   getProfileData: (token: string) =>
     request.postForm<PostGetProfileData>(API.GET_USER_SETTINGS_DATA, { token }),
@@ -116,5 +209,7 @@ export const userApi = {
   getProfileInfoPublic: (uid: number) =>
     request.postForm<Exclude<PostGetProfileInfoReturn, { email: null }>>(API.PROFILE_INFO_PUBLIC, {
       uid
-    })
+    }),
+  getProfileRegions: (uid: number, type: string) =>
+    request.postForm<PostGetProfileRegionsReturn>(API.GET_PROFILE_REGIONS, { uid, type })
 };

+ 2 - 1
src/modules/api/user/user-query-keys.tsx

@@ -2,5 +2,6 @@ export const userQueryKeys = {
   getProfileData: () => ['getProfileData'] as const,
   setProfileData: () => ['setProfileData'] as const,
   getProfileInfo: () => ['getProfileInfo'] as const,
-  getProfileInfoPublic: () => ['getProfileInfoPublic'] as const
+  getProfileInfoPublic: () => ['getProfileInfoPublic'] as const,
+  getProfileRegions: (uid: number, type: string) => ['getProfileRegions', uid, type] as const
 };

+ 399 - 0
src/screens/InAppScreens/ProfileScreen/RegionsRenderer/index.tsx

@@ -0,0 +1,399 @@
+import React, { useCallback } from 'react';
+import { View, Text, Image, TouchableOpacity, ScrollView } from 'react-native';
+import { Colors } from 'src/theme';
+import { styles } from './styles';
+import { Loading } from 'src/components';
+
+import CheckSvg from 'assets/icons/mark.svg';
+import CloseSVG from 'assets/icons/close.svg';
+import { API_HOST } from 'src/constants';
+import { FlashList } from '@shopify/flash-list';
+
+interface Mega {
+  id: number;
+  name: string;
+  regions: {
+    id: number;
+    name: string;
+    flag1: string;
+    flag2: string | null;
+  }[];
+  transits: number[];
+  visits: number[];
+}
+
+interface Region {
+  id: number;
+  name: string;
+  flag1: string;
+  flag2: string | null;
+  country_id: number;
+}
+
+const RegionsRenderer = ({
+  type,
+  regions,
+  setIsModalVisible
+}: {
+  type: string;
+  regions: any;
+  setIsModalVisible: (value: boolean) => void;
+}) => {
+  const flashlistConfig = {
+    waitForInteraction: true,
+    itemVisiblePercentThreshold: 50,
+    minimumViewTime: 1000
+  };
+  const getOpacity = useCallback(
+    (item: any, mega?: Mega) => {
+      switch (type) {
+        case 'nm':
+        case 'mqp':
+          return mega?.visits.includes(item.id) ? 1 : 0.4;
+        case 'un':
+        case 'unp':
+        case 'tcc':
+          return regions.data[1].includes(item.name) ? 1 : 0.4;
+        case 'slow':
+          return item.visited === 1 ? 1 : 0.4;
+        case 'whs':
+          return item.visited ? 1 : 0.4;
+        default:
+          return 1;
+      }
+    },
+    [type, regions?.data]
+  );
+
+  const renderRegion = useCallback(
+    (item: any, mega?: Mega) => {
+      return (
+        <View style={[styles.regionRow, { opacity: getOpacity(item, mega) }]}>
+          <View
+            style={[
+              styles.flags,
+              type === 'yes' ? { flex: 2 } : type === 'slow' ? { flex: 1 } : {}
+            ]}
+          >
+            <Image
+              source={{
+                uri:
+                  API_HOST +
+                  (type === 'whs'
+                    ? item.flag
+                    : type === 'slow'
+                      ? '/img/flags_new/' + item?.flag
+                      : '/img/flags_new/' + item?.flag1)
+              }}
+              style={styles.regionsFlag}
+            />
+            {item?.flag2 ? (
+              <Image
+                source={{ uri: API_HOST + '/img/flags_new/' + item.flag2 }}
+                style={[styles.regionsFlag, { marginLeft: -18 }]}
+              />
+            ) : null}
+            <View style={styles.regionInfo}>
+              <Text style={styles.regionName}>{type === 'slow' ? item.country : item?.name}</Text>
+            </View>
+          </View>
+          {type === 'nm' && (
+            <View style={{ flexDirection: 'row', flex: 2 }}>
+              <View style={{ width: '56%', alignItems: 'center', marginRight: '2%' }}>
+                {regions.data.firsts[item?.id] && regions.data.firsts[item?.id] !== 1 ? (
+                  <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
+                    {regions.data.firsts[item?.id]}
+                  </Text>
+                ) : null}
+              </View>
+              <View style={{ width: '42%', alignItems: 'center' }}>
+                {regions.data.last[item?.id] && regions.data.last[item?.id] !== 1 ? (
+                  <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
+                    {regions.data.last[item?.id]}
+                  </Text>
+                ) : null}
+              </View>
+            </View>
+          )}
+          {type === 'yes' && (
+            <View style={{ flexDirection: 'row', flex: 1 }}>
+              <View style={{ width: '60%', alignItems: 'center' }}>
+                {regions.data[1][item?.id] && regions.data[1][item?.id].year !== 1 ? (
+                  <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
+                    {regions.data[1][item?.id].year}
+                  </Text>
+                ) : null}
+              </View>
+              <View style={{ width: '40%', alignItems: 'center' }}>
+                {regions.data[1][item?.id] ? (
+                  <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
+                    {regions.data[1][item?.id].score}
+                  </Text>
+                ) : null}
+              </View>
+            </View>
+          )}
+          {type === 'slow' && (
+            <View style={{ flexDirection: 'row', flex: 1 }}>
+              <View style={{ width: '33%', alignItems: 'center' }}>
+                {item.slow11 === 1 ? (
+                  <CheckSvg fill={Colors.DARK_BLUE} width={14} height={14} />
+                ) : null}
+              </View>
+              <View style={{ width: '33%', alignItems: 'center' }}>
+                {item.slow31 === 1 ? (
+                  <CheckSvg fill={Colors.DARK_BLUE} width={14} height={14} />
+                ) : null}
+              </View>
+              <View style={{ width: '33%', alignItems: 'center' }}>
+                {item.slow101 === 1 ? (
+                  <CheckSvg fill={Colors.DARK_BLUE} width={14} height={14} />
+                ) : null}
+              </View>
+            </View>
+          )}
+        </View>
+      );
+    },
+    [getOpacity, regions?.data, type]
+  );
+
+  const renderMegaregion = useCallback(
+    ({ item }: { item: Mega }) => {
+      return (
+        <View style={styles.megaregion}>
+          <View style={styles.blockTitle}>
+            <Text style={[styles.megaregionTitle, type === 'mqp' ? { textAlign: 'center' } : {}]}>
+              {type === 'nm' ? '- ' : ''}
+              {item.name}
+              <Text style={{ fontWeight: '600' }}>
+                {' '}
+                - {item.visits.length}/{item.regions.length} (
+                {((item.visits.length * 100) / item.regions.length).toFixed(2)}%)
+              </Text>
+            </Text>
+            {type === 'nm' && (
+              <View style={{ flexDirection: 'row', flex: 2 }}>
+                <View style={{ width: '56%', alignItems: 'center', marginRight: '2%' }}>
+                  <Text style={styles.alignCenter}>
+                    <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>First</Text>
+                    {'\n'}
+                    <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>visited</Text>
+                  </Text>
+                </View>
+                <View style={{ width: '42%', alignItems: 'center' }}>
+                  <Text style={styles.alignCenter}>
+                    <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Last</Text>
+                    {'\n'}
+                    <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>visit</Text>
+                  </Text>
+                </View>
+              </View>
+            )}
+          </View>
+
+          <View style={styles.megaSubList}>
+            <ScrollView
+              style={{ flex: 1 }}
+              nestedScrollEnabled={true}
+              showsVerticalScrollIndicator={false}
+              horizontal={false}
+            >
+              {item.regions?.map((region: any) => (
+                <View key={region.id}>{renderRegion(region, item)}</View>
+              ))}
+            </ScrollView>
+          </View>
+        </View>
+      );
+    },
+    [flashlistConfig, renderRegion, type]
+  );
+
+  const renderContent = () => {
+    const renderHeader = (headerText: string) => (
+      <RegionsModalHeader textHeader={headerText} onRequestClose={() => setIsModalVisible(false)} />
+    );
+
+    switch (type) {
+      case 'nm':
+      case 'mqp':
+        return (
+          <>
+            {renderHeader(type === 'nm' ? 'NM' : 'DARE')}
+            <FlashList
+              viewabilityConfig={flashlistConfig}
+              estimatedItemSize={4000}
+              data={regions.data.megaregions}
+              renderItem={renderMegaregion}
+              keyExtractor={(megaregion) => megaregion?.id?.toString()}
+              showsVerticalScrollIndicator={false}
+              nestedScrollEnabled={true}
+              contentContainerStyle={{ paddingTop: 8 }}
+            />
+          </>
+        );
+      case 'un':
+      case 'unp':
+      case 'tcc':
+        return (
+          <>
+            {renderHeader(type === 'un' ? 'UN' : type === 'unp' ? 'UN+' : 'TCC')}
+            <FlashList
+              viewabilityConfig={flashlistConfig}
+              estimatedItemSize={50}
+              data={regions.data[0]}
+              renderItem={(region) => renderRegion(region.item)}
+              keyExtractor={(region: Region) => region.name}
+              showsVerticalScrollIndicator={false}
+              contentContainerStyle={{ paddingTop: 8 }}
+              ListHeaderComponent={() => (
+                <View style={styles.blockTitle}>
+                  <Text style={[styles.megaregionTitle, { textAlign: 'center' }]}>
+                    {type === 'un' ? 'Countries' : 'Territories'}
+                    <Text style={{ fontWeight: '600' }}>
+                      {' '}
+                      - {regions.data[3]}/{regions.data[2]} (
+                      {((regions.data[3] * 100) / regions.data[2]).toFixed(2)}%)
+                    </Text>
+                  </Text>
+                </View>
+              )}
+            />
+          </>
+        );
+      case 'slow':
+        return (
+          <>
+            {renderHeader('SLOW')}
+            <FlashList
+              viewabilityConfig={flashlistConfig}
+              estimatedItemSize={50}
+              data={regions.data[0]}
+              renderItem={(region) => renderRegion(region.item)}
+              keyExtractor={(region: Region) => region.country_id.toString()}
+              showsVerticalScrollIndicator={false}
+              contentContainerStyle={{ paddingTop: 8 }}
+              ListHeaderComponent={() => {
+                return (
+                  <View style={styles.blockTitle}>
+                    <Text style={[styles.megaregionTitle, { flex: 1 }]}>Countries</Text>
+                    <View style={{ flexDirection: 'row', flex: 1 }}>
+                      <View style={styles.slow}>
+                        <Text style={styles.alignCenter}>
+                          <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Slow11</Text>
+                        </Text>
+                      </View>
+                      <View style={styles.slow}>
+                        <Text style={styles.alignCenter}>
+                          <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Slow31</Text>
+                        </Text>
+                      </View>
+                      <View style={styles.slow}>
+                        <Text style={styles.alignCenter}>
+                          <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Slow101</Text>
+                        </Text>
+                      </View>
+                    </View>
+                  </View>
+                );
+              }}
+            />
+          </>
+        );
+      case 'yes':
+        const calcTotalScore = (
+          values: {
+            year: number;
+            score: number;
+            country: string;
+          }[]
+        ) => {
+          const totalScore = values.reduce((accumulator, item) => {
+            return accumulator + item.score;
+          }, 0);
+          return totalScore;
+        };
+        return (
+          <>
+            {renderHeader('YES')}
+            <FlashList
+              viewabilityConfig={flashlistConfig}
+              estimatedItemSize={50}
+              data={regions.data[0]}
+              renderItem={(region) => renderRegion(region.item)}
+              keyExtractor={(region: Region) => region.id.toString()}
+              showsVerticalScrollIndicator={false}
+              contentContainerStyle={{ paddingTop: 8 }}
+              ListHeaderComponent={() => {
+                return (
+                  <View style={styles.blockTitle}>
+                    <Text style={[styles.megaregionTitle, { flex: 2 }]}>
+                      Countries
+                      <Text style={{ fontWeight: '600' }}>
+                        {' total score '}
+                        {calcTotalScore(Object.values(regions.data[1]))}
+                      </Text>
+                    </Text>
+                    <View style={{ flexDirection: 'row', flex: 1 }}>
+                      <View style={{ width: '60%', alignItems: 'center' }}>
+                        <Text style={styles.alignCenter}>
+                          <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Last visit</Text>
+                        </Text>
+                      </View>
+                      <View
+                        style={{ width: '40%', alignItems: 'center', justifyContent: 'center' }}
+                      >
+                        <Text style={styles.alignCenter}>
+                          <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Score</Text>
+                        </Text>
+                      </View>
+                    </View>
+                  </View>
+                );
+              }}
+            />
+          </>
+        );
+      case 'whs':
+        return (
+          <>
+            {renderHeader('WHS')}
+            <FlashList
+              viewabilityConfig={flashlistConfig}
+              estimatedItemSize={50}
+              data={regions.data}
+              renderItem={(region) => renderRegion(region.item)}
+              keyExtractor={(region: Region) => region.name}
+              showsVerticalScrollIndicator={false}
+              contentContainerStyle={{ paddingTop: 8 }}
+            />
+          </>
+        );
+    }
+  };
+
+  return <View style={styles.modalContent}>{regions?.data ? renderContent() : <Loading />}</View>;
+};
+
+const RegionsModalHeader = ({
+  textHeader,
+  onRequestClose,
+  rightElement
+}: {
+  textHeader: string;
+  onRequestClose: () => void;
+  rightElement?: any;
+}) => {
+  return (
+    <View style={styles.header}>
+      <TouchableOpacity onPress={onRequestClose} style={{ padding: 6 }}>
+        <CloseSVG />
+      </TouchableOpacity>
+      <Text style={styles.headerText}>{textHeader}</Text>
+      {rightElement ? rightElement : <View style={{ height: 30, width: 30 }} />}
+    </View>
+  );
+};
+
+export default RegionsRenderer;

+ 80 - 0
src/screens/InAppScreens/ProfileScreen/RegionsRenderer/styles.tsx

@@ -0,0 +1,80 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from '../../../../theme';
+import { getFontSize } from '../../../../utils';
+
+export const styles = StyleSheet.create({
+  modalContent: {
+    backgroundColor: 'white',
+    borderRadius: 15,
+    paddingHorizontal: 16,
+    gap: 12,
+    paddingVertical: 12,
+    height: '90%'
+  },
+  megaregion: {
+    marginBottom: 16
+  },
+  megaregionTitle: {
+    fontWeight: '700',
+    fontSize: 13,
+    color: Colors.DARK_BLUE,
+    flex: 7
+  },
+  regionRow: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginBottom: 12,
+    justifyContent: 'space-between',
+    gap: 6
+  },
+  flags: {
+    flexDirection: 'row',
+    gap: 8,
+    alignItems: 'center',
+    flex: 7
+  },
+  regionInfo: {
+    flexShrink: 1
+  },
+  regionName: {
+    fontSize: 12,
+    fontWeight: '600',
+    color: Colors.DARK_BLUE
+  },
+  regionDates: {
+    fontSize: 14,
+    color: 'gray'
+  },
+  regionsFlag: {
+    width: 32,
+    height: 32,
+    borderRadius: 32 / 2,
+    borderWidth: 1,
+    borderColor: Colors.FILL_LIGHT
+  },
+  header: {
+    width: '100%',
+    display: 'flex',
+    justifyContent: 'space-between',
+    flexDirection: 'row',
+    alignItems: 'center'
+  },
+  headerText: {
+    fontFamily: 'redhat-600',
+    fontSize: getFontSize(14),
+    color: Colors.DARK_BLUE
+  },
+  alignCenter: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    textAlign: 'center'
+  },
+  blockTitle: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    marginBottom: 12
+  },
+  slow: { width: '33%', alignItems: 'center', justifyContent: 'center' },
+  megaSubList: { flex: 1, minHeight: 20, width: '100%' }
+});

+ 167 - 97
src/screens/InAppScreens/ProfileScreen/index.tsx

@@ -2,13 +2,15 @@ import React, { FC, ReactNode, useEffect, useState } from 'react';
 import { Linking, ScrollView, Text, TouchableOpacity, View } from 'react-native';
 import { Image } from 'expo-image';
 import { CommonActions, NavigationProp, useNavigation } from '@react-navigation/native';
+import Modal from 'react-native-modal';
 
 import {
   type Score,
   type Series,
   type SocialData,
   usePostGetProfileInfoPublicQuery,
-  usePostGetProfileInfoQuery
+  usePostGetProfileInfoQuery,
+  usePostGetProfileRegions
 } from '@api/user';
 
 import {
@@ -37,6 +39,7 @@ import IconLink from '../../../../assets/icons/link.svg';
 import GearIcon from '../../../../assets/icons/gear.svg';
 import ArrowIcon from '../../../../assets/icons/next.svg';
 import axios from 'axios';
+import RegionsRenderer from './RegionsRenderer';
 
 type Props = {
   navigation: NavigationProp<any>;
@@ -47,6 +50,8 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
   const isPublicView = route.name === NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW;
 
   const token = storage.get('token', StoreType.STRING) as string;
+  const currentUserId = storage.get('uid', StoreType.STRING) as string;
+
   const [isUserMapAvailable, setIsUserMapAvailable] = useState<boolean>(false);
   const [loading, setLoading] = useState(true);
 
@@ -157,6 +162,7 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
           homebase2: data.homebase2_name,
           series: data.series
         }}
+        userId={isPublicView ? route.params?.userId : +currentUserId}
       />
     </PageWrapper>
   );
@@ -179,10 +185,16 @@ type PersonalInfoProps = {
     homebase2: string;
     series: Series[];
   };
+  userId: number;
 };
 
-const PersonalInfo: FC<PersonalInfoProps> = ({ data }) => {
+const PersonalInfo: FC<PersonalInfoProps> = ({ data, userId }) => {
   const [showMoreSeries, setShowMoreSeries] = useState(false);
+  const [type, setType] = useState<string>('nm');
+  const [isModalVisible, setIsModalVisible] = useState(false);
+
+  const { data: regions } = usePostGetProfileRegions(userId, type);
+
   const scores = ['NM1301', 'DARE', 'UN', 'UN+', 'TCC', 'SLOW', 'YES', 'WHS'];
 
   const handleOpenUrl = (url: string | undefined) => {
@@ -200,111 +212,169 @@ const PersonalInfo: FC<PersonalInfoProps> = ({ data }) => {
     );
   };
 
+  const handleOpenModal = (type: string) => {
+    switch (type) {
+      case 'NM1301':
+        setType('nm');
+        break;
+      case 'DARE':
+        setType('mqp');
+        break;
+      case 'UN':
+        setType('un');
+        break;
+      case 'UN+':
+        setType('unp');
+        break;
+      case 'TCC':
+        setType('tcc');
+        break;
+      case 'SLOW':
+        setType('slow');
+        break;
+      case 'YES':
+        setType('yes');
+        break;
+      case 'WHS':
+        setType('whs');
+        break;
+    }
+    setIsModalVisible(true);
+  };
+
   return (
-    <ScrollView
-      showsVerticalScrollIndicator={false}
-      contentContainerStyle={{ gap: 20, paddingBottom: 32}}
-      style={{ paddingTop: 20 }}
-    >
-      <InfoItem inline={true} title={'RANKING'}>
-        <View style={{ flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }}>
-          {scores.map((score, index) => {
-            let scoreRank = data.scores?.find((s) => s.name === score && s.score > 0)?.score ?? '-';
+    <>
+      <ScrollView
+        showsVerticalScrollIndicator={false}
+        contentContainerStyle={{ gap: 20, paddingBottom: 32 }}
+        style={{ paddingTop: 20 }}
+      >
+        <InfoItem inline={true} title={'RANKING'}>
+          <View style={{ flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }}>
+            {scores.map((score, index) => {
+              let scoreRank =
+                data.scores?.find((s) => s.name === score && s.score > 0)?.score ?? '-';
 
-            if (score === 'YES' && +scoreRank >= 4500) {
-              scoreRank = '-';
-            }
+              if (score === 'YES' && +scoreRank >= 4500) {
+                scoreRank = '-';
+              }
 
-            return (
-              <View key={index} style={styles.rankingItem}>
-                <Text style={styles.rankingScore}>{scoreRank}</Text>
-                <Text style={[styles.titleText, { flex: 0 }]}>{score}</Text>
+              return (
+                <TouchableOpacity
+                  key={index}
+                  style={styles.rankingItem}
+                  onPress={() => handleOpenModal(score)}
+                >
+                  <Text style={styles.rankingScore}>{scoreRank}</Text>
+                  <Text style={[styles.titleText, { flex: 0 }]}>
+                    {score === 'NM1301' ? 'NM' : score}
+                  </Text>
+                </TouchableOpacity>
+              );
+            })}
+          </View>
+        </InfoItem>
+        {data.series?.length > 0 && (
+          <InfoItem showMore={showMoreSeries} inline={true} title={'SERIES'}>
+            {data.series?.slice(0, showMoreSeries ? data.series.length : 8).map((data, index) => (
+              <View
+                key={index}
+                style={{ display: 'flex', flexDirection: 'column', gap: 5, alignItems: 'center' }}
+              >
+                <Image
+                  source={{ uri: API_HOST + data.icon_png }}
+                  style={{ width: 28, height: 28 }}
+                />
+                <Text style={[styles.headerText, { flex: 0 }]}>{data.score}</Text>
               </View>
-            );
-          })}
-        </View>
-      </InfoItem>
-      {data.series?.length > 0 && (
-        <InfoItem showMore={showMoreSeries} inline={true} title={'SERIES BADGES'}>
-          {data.series?.slice(0, showMoreSeries ? data.series.length : 8).map((data, index) => (
+            ))}
+          </InfoItem>
+        )}
+        {data.series?.length > 8 ? (
+          <TouchableOpacity onPress={() => setShowMoreSeries(!showMoreSeries)}>
             <View
-              key={index}
-              style={{ display: 'flex', flexDirection: 'column', gap: 5, alignItems: 'center' }}
+              style={[
+                { alignItems: 'center' },
+                showMoreSeries
+                  ? { transform: 'rotate(180deg)', paddingTop: 8 }
+                  : { paddingBottom: 8 }
+              ]}
             >
-              <Image source={{ uri: API_HOST + data.icon_png }} style={{ width: 28, height: 28 }} />
-              <Text style={[styles.headerText, { flex: 0 }]}>{data.score}</Text>
+              <ArrowIcon stroke={'#B7C6CB'} />
             </View>
-          ))}
-        </InfoItem>
-      )}
-      {data.series?.length > 8 ? (
-        <TouchableOpacity onPress={() => setShowMoreSeries(!showMoreSeries)}>
-          <View
-            style={[
-              { alignItems: 'center' },
-              showMoreSeries ? { transform: 'rotate(180deg)', paddingTop: 8 } : { paddingBottom: 8 }
-            ]}
-          >
-            <ArrowIcon stroke={'#B7C6CB'} />
-          </View>
-        </TouchableOpacity>
-      ) : null}
-      <View style={{ display: 'flex', flexDirection: 'row' }}>
-        <Text style={styles.headerText}>DATE OF BIRTH</Text>
-        <Text style={styles.titleText}>{new Date(data.date_of_birth).toDateString()}</Text>
-      </View>
-      <View style={{ display: 'flex', flexDirection: 'row' }}>
-        <Text style={styles.headerText}>REGION OF ORIGIN</Text>
-        <Text style={styles.titleText}>{data.homebase}</Text>
-      </View>
-      {data.homebase2 ? (
+          </TouchableOpacity>
+        ) : null}
+        {/* <View style={{ display: 'flex', flexDirection: 'row' }}>
+          <Text style={styles.headerText}>DATE OF BIRTH</Text>
+          <Text style={styles.titleText}>{new Date(data.date_of_birth).toDateString()}</Text>
+        </View>
         <View style={{ display: 'flex', flexDirection: 'row' }}>
-          <Text style={styles.headerText}>SECOND REGION</Text>
-          <Text style={styles.titleText}>{data.homebase2}</Text>
+          <Text style={styles.headerText}>REGION OF ORIGIN</Text>
+          <Text style={styles.titleText}>{data.homebase}</Text>
         </View>
-      ) : null}
-      {data.bio && data.bio.length > 0 && (
-        <InfoItem title={'BIO'}>
-          <Text style={[styles.titleText, { flex: 0 }]}>{data.bio}</Text>
-        </InfoItem>
-      )}
-      {hasActiveLinks() && (
-        <InfoItem title={'SOCIAL LINKS'}>
-          <View style={styles.linksBox}>
-            {data.links?.f?.link && data.links?.f?.active !== 0 ? (
-              <TouchableOpacity onPress={() => handleOpenUrl(data.links?.f?.link)}>
-                <IconFacebook fill={Colors.DARK_BLUE} />
-              </TouchableOpacity>
-            ) : null}
-            {data.links?.i?.link && data.links?.i?.active !== 0 ? (
-              <TouchableOpacity onPress={() => handleOpenUrl(data.links?.i?.link)}>
-                <IconInstagram fill={Colors.DARK_BLUE} />
-              </TouchableOpacity>
-            ) : null}
-            {data.links?.t?.link && data.links?.t?.active !== 0 ? (
-              <TouchableOpacity onPress={() => handleOpenUrl(data.links?.t?.link)}>
-                <IconTwitter fill={Colors.DARK_BLUE} />
-              </TouchableOpacity>
-            ) : null}
-            {data.links?.y?.link && data.links?.y?.active !== 0 ? (
-              <TouchableOpacity onPress={() => handleOpenUrl(data.links?.y?.link)}>
-                <IconYouTube fill={Colors.DARK_BLUE} />
-              </TouchableOpacity>
-            ) : null}
-            {data.links?.www?.link && data.links?.www?.active !== 0 ? (
-              <TouchableOpacity onPress={() => handleOpenUrl(data.links?.www?.link)}>
-                <IconGlobe fill={Colors.DARK_BLUE} />
-              </TouchableOpacity>
-            ) : null}
-            {data.links?.other?.link && data.links?.other?.active !== 0 ? (
-              <TouchableOpacity onPress={() => handleOpenUrl(data.links?.other?.link)}>
-                <IconLink fill={Colors.DARK_BLUE} />
-              </TouchableOpacity>
-            ) : null}
+        {data.homebase2 ? (
+          <View style={{ display: 'flex', flexDirection: 'row' }}>
+            <Text style={styles.headerText}>SECOND REGION</Text>
+            <Text style={styles.titleText}>{data.homebase2}</Text>
           </View>
-        </InfoItem>
-      )}
-    </ScrollView>
+        ) : null} */}
+        {data.bio && data.bio.length > 0 && (
+          <InfoItem title={'BIO'}>
+            <Text style={[styles.titleText, { flex: 0 }]}>{data.bio}</Text>
+          </InfoItem>
+        )}
+        {hasActiveLinks() && (
+          <InfoItem title={'SOCIAL LINKS'}>
+            <View style={styles.linksBox}>
+              {data.links?.f?.link && data.links?.f?.active !== 0 ? (
+                <TouchableOpacity onPress={() => handleOpenUrl(data.links?.f?.link)}>
+                  <IconFacebook fill={Colors.DARK_BLUE} />
+                </TouchableOpacity>
+              ) : null}
+              {data.links?.i?.link && data.links?.i?.active !== 0 ? (
+                <TouchableOpacity onPress={() => handleOpenUrl(data.links?.i?.link)}>
+                  <IconInstagram fill={Colors.DARK_BLUE} />
+                </TouchableOpacity>
+              ) : null}
+              {data.links?.t?.link && data.links?.t?.active !== 0 ? (
+                <TouchableOpacity onPress={() => handleOpenUrl(data.links?.t?.link)}>
+                  <IconTwitter fill={Colors.DARK_BLUE} />
+                </TouchableOpacity>
+              ) : null}
+              {data.links?.y?.link && data.links?.y?.active !== 0 ? (
+                <TouchableOpacity onPress={() => handleOpenUrl(data.links?.y?.link)}>
+                  <IconYouTube fill={Colors.DARK_BLUE} />
+                </TouchableOpacity>
+              ) : null}
+              {data.links?.www?.link && data.links?.www?.active !== 0 ? (
+                <TouchableOpacity onPress={() => handleOpenUrl(data.links?.www?.link)}>
+                  <IconGlobe fill={Colors.DARK_BLUE} />
+                </TouchableOpacity>
+              ) : null}
+              {data.links?.other?.link && data.links?.other?.active !== 0 ? (
+                <TouchableOpacity onPress={() => handleOpenUrl(data.links?.other?.link)}>
+                  <IconLink fill={Colors.DARK_BLUE} />
+                </TouchableOpacity>
+              ) : null}
+            </View>
+          </InfoItem>
+        )}
+      </ScrollView>
+      <Modal
+        isVisible={isModalVisible}
+        onBackdropPress={() => setIsModalVisible(false)}
+        onBackButtonPress={() => setIsModalVisible(false)}
+        style={styles.modal}
+        statusBarTranslucent={true}
+        presentationStyle="overFullScreen"
+      >
+        <RegionsRenderer
+          type={type}
+          regions={regions}
+          setIsModalVisible={setIsModalVisible}
+        />
+      </Modal>
+    </>
   );
 };
 

+ 9 - 1
src/screens/InAppScreens/ProfileScreen/styles.ts

@@ -40,7 +40,11 @@ export const styles = StyleSheet.create({
     margin: '1%',
     display: 'flex',
     flexDirection: 'column',
-    alignItems: 'center'
+    alignItems: 'center',
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 4,
+    padding: 4,
+    gap: 1 //
   },
   rankingScore: {
     flex: 0,
@@ -85,5 +89,9 @@ export const styles = StyleSheet.create({
     right: -10,
     bottom: -10,
     justifyContent: 'center'
+  },
+  modal: {
+    justifyContent: 'flex-end',
+    margin: 0,
   }
 });

+ 4 - 2
src/types/api.ts

@@ -81,7 +81,8 @@ export enum API_ENDPOINT {
   DELETE_USER = 'delete-user',
   GET_LAST_REGIONS_UPDATE = 'last-regions-db-update',
   GET_LAST_DARE_UPDATE = 'last-dare-db-update',
-  GET_SERVERS = 'get-servers'
+  GET_SERVERS = 'get-servers',
+  GET_PROFILE_REGIONS = 'get-profile'
 }
 
 export enum API {
@@ -146,7 +147,8 @@ export enum API {
   DELETE_USER = `${API_ROUTE.APP}/${API_ENDPOINT.DELETE_USER}`,
   GET_LAST_REGIONS_DB_UPDATE = `${API_ROUTE.APP}/${API_ENDPOINT.GET_LAST_REGIONS_UPDATE}`,
   GET_LAST_DARE_DB_UPDATE = `${API_ROUTE.APP}/${API_ENDPOINT.GET_LAST_DARE_UPDATE}`,
-  GET_SERVERS = `${API_ROUTE.APP}/${API_ENDPOINT.GET_SERVERS}`
+  GET_SERVERS = `${API_ROUTE.APP}/${API_ENDPOINT.GET_SERVERS}`,
+  GET_PROFILE_REGIONS = `${API_ROUTE.REGIONS}/${API_ENDPOINT.GET_PROFILE_REGIONS}`
 }
 
 export type BaseAxiosError = AxiosError;