Browse Source

countries screen

Viktoriia 1 year ago
parent
commit
200063f5c9

+ 2 - 0
Route.tsx

@@ -37,6 +37,7 @@ import AddPhotoScreen from 'src/screens/InAppScreens/TravelsScreen/AddPhotoScree
 import TripsScreen from 'src/screens/InAppScreens/TravelsScreen/TripsScreen';
 import AddNewTripScreen from 'src/screens/InAppScreens/TravelsScreen/AddNewTripScreen';
 import AddRegionsScreen from 'src/screens/InAppScreens/TravelsScreen/AddRegionsScreen';
+import CountriesScreen from 'src/screens/InAppScreens/TravelsScreen/CountriesScreen';
 
 import { NAVIGATION_PAGES } from './src/types';
 import { storage, StoreType } from './src/storage';
@@ -177,6 +178,7 @@ const Route = () => {
                   <ScreenStack.Screen name={NAVIGATION_PAGES.TRIPS} component={TripsScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_TRIP} component={AddNewTripScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_REGIONS} component={AddRegionsScreen} />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.COUNTRIES} component={CountriesScreen} />
                 </ScreenStack.Navigator>
               )}
             </BottomTab.Screen>

+ 10 - 0
assets/icons/travels-screens/circle-check-regular.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2202_20286)">
+<path d="M8 1.50195C9.72391 1.50195 11.3772 2.18677 12.5962 3.40576C13.8152 4.62475 14.5 6.27805 14.5 8.00195C14.5 9.72586 13.8152 11.3792 12.5962 12.5981C11.3772 13.8171 9.72391 14.502 8 14.502C6.27609 14.502 4.62279 13.8171 3.40381 12.5981C2.18482 11.3792 1.5 9.72586 1.5 8.00195C1.5 6.27805 2.18482 4.62475 3.40381 3.40576C4.62279 2.18677 6.27609 1.50195 8 1.50195ZM8 16.002C10.1217 16.002 12.1566 15.1591 13.6569 13.6588C15.1571 12.1585 16 10.1237 16 8.00195C16 5.88022 15.1571 3.84539 13.6569 2.3451C12.1566 0.844808 10.1217 0.00195313 8 0.00195312C5.87827 0.00195313 3.84344 0.844808 2.34315 2.3451C0.842855 3.84539 0 5.88022 0 8.00195C0 10.1237 0.842855 12.1585 2.34315 13.6588C3.84344 15.1591 5.87827 16.002 8 16.002ZM11.5312 6.5332C11.825 6.23945 11.825 5.76445 11.5312 5.47383C11.2375 5.1832 10.7625 5.18008 10.4719 5.47383L7.00313 8.94258L5.53438 7.47383C5.24063 7.18008 4.76562 7.18008 4.475 7.47383C4.18437 7.76758 4.18125 8.24258 4.475 8.5332L6.475 10.5332C6.76875 10.827 7.24375 10.827 7.53438 10.5332L11.5312 6.5332Z" fill="#C8C8C8"/>
+</g>
+<defs>
+<clipPath id="clip0_2202_20286">
+<rect width="16" height="16" fill="white" transform="translate(0 0.00195312)"/>
+</clipPath>
+</defs>
+</svg>

+ 4 - 0
assets/icons/travels-screens/info.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 1.50195C11.5899 1.50195 14.5 4.4121 14.5 8.00195C14.5 11.5918 11.5899 14.502 8 14.502C4.41014 14.502 1.5 11.5918 1.5 8.00195C1.5 4.4121 4.41014 1.50195 8 1.50195ZM8 16.002C12.4183 16.002 16 12.4202 16 8.00195C16 3.58368 12.4183 0.00195312 8 0.00195312C3.58172 0.00195312 0 3.58368 0 8.00195C0 12.4202 3.58172 16.002 8 16.002Z" fill="#0F3F4F"/>
+<path d="M8.08319 3.94289C8.00661 3.93665 7.9281 3.9388 7.84875 3.95021C7.32804 4.02509 6.9375 4.47584 6.9375 5.00191C6.9375 5.58501 7.4169 6.06441 8 6.06441C8.5831 6.06441 9.0625 5.58501 9.0625 5.00191C9.06251 4.44076 8.61926 3.98652 8.08319 3.94289ZM7 6.43941C6.55512 6.43941 6.1875 6.80703 6.1875 7.25191C6.1875 7.69678 6.55512 8.06441 7 8.06441H7.1875V10.4394H7C6.55512 10.4394 6.1875 10.807 6.1875 11.2519C6.1875 11.6968 6.55512 12.0644 7 12.0644H9C9.44488 12.0644 9.8125 11.6968 9.8125 11.2519C9.8125 10.807 9.44488 10.4394 9 10.4394H8.8125V7.25191C8.8125 6.80703 8.44488 6.43941 8 6.43941H7Z" fill="#0F3F4F"/>
+</svg>

+ 35 - 0
src/modules/api/countries/countries-api.tsx

@@ -0,0 +1,35 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetSlowReturn extends ResponseType {
+  slow: {
+    country_id: number;
+    country: string;
+    flag: string;
+    mega: number[];
+    visited: 0 | 1;
+    slow11: 0 | 1;
+    slow31: 0 | 1;
+    slow101: 0 | 1;
+    yes: number;
+  }[];
+  megaregions: {
+    id: number;
+    name: string;
+  }[];
+}
+
+export interface PostSetSlow {
+  token: string;
+  id: number;
+  v: boolean;
+  s11: boolean;
+  s31: boolean;
+  s101: boolean;
+}
+
+export const countriesApi = {
+  getSlow: (token: string) => request.postForm<PostGetSlowReturn>(API.GET_SLOW, { token }),
+  setSlow: (data: PostSetSlow) => request.postForm<ResponseType>(API.SET_SLOW, data)
+};

+ 4 - 0
src/modules/api/countries/countries-query-keys.tsx

@@ -0,0 +1,4 @@
+export const countriesQueryKeys = {
+  getSlow: (token: string) => ['getTripsYears', { token }] as const,
+  setSlow: () => ['setNewTrip'] as const
+};

+ 3 - 0
src/modules/api/countries/index.ts

@@ -0,0 +1,3 @@
+export * from './queries';
+export * from './countries-api';
+export * from './countries-query-keys';

+ 2 - 0
src/modules/api/countries/queries/index.ts

@@ -0,0 +1,2 @@
+export * from './use-post-get-slow';
+export * from './use-post-set-slow';

+ 17 - 0
src/modules/api/countries/queries/use-post-get-slow.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { countriesQueryKeys } from '../countries-query-keys';
+import { countriesApi, type PostGetSlowReturn } from '../countries-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetSlowQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetSlowReturn, BaseAxiosError>({
+    queryKey: countriesQueryKeys.getSlow(token),
+    queryFn: async () => {
+      const response = await countriesApi.getSlow(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/countries/queries/use-post-set-slow.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { countriesQueryKeys } from '../countries-query-keys';
+import { type PostSetSlow, countriesApi } from '../countries-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostSetSlowMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostSetSlow, ResponseType>({
+    mutationKey: countriesQueryKeys.setSlow(),
+    mutationFn: async (data) => {
+      const response = await countriesApi.setSlow(data);
+      return response.data;
+    }
+  });
+};

+ 86 - 0
src/screens/InAppScreens/TravelsScreen/Components/CountryItem/index.tsx

@@ -0,0 +1,86 @@
+import React from 'react';
+import { View, Text, Image, TouchableOpacity } from 'react-native';
+
+import { SlowData } from '../../utils/types';
+import { Colors } from 'src/theme';
+import { API_HOST } from 'src/constants';
+import { styles } from './styles';
+
+import CheckSvg from 'assets/icons/travels-screens/circle-check.svg';
+import CheckRegularSvg from 'assets/icons/travels-screens/circle-check-regular.svg';
+import EditSvg from 'assets/icons/travels-screens/pen-to-square.svg';
+import MarkIcon from 'assets/icons/mark.svg';
+
+const CountryItem = React.memo(
+  ({
+    item,
+    updateSlow,
+    openEditModal
+  }: {
+    item: SlowData;
+    updateSlow: (id: number, v: boolean, s11: boolean, s31: boolean, s101: boolean) => void;
+    openEditModal: (item: SlowData) => void;
+  }) => {
+    const renderDurationIcon = (condition: 0 | 1) =>
+      condition ? <CheckSvg fill={Colors.DARK_BLUE} /> : <CheckRegularSvg />;
+
+    return (
+      <View style={styles.countryItem}>
+        <View style={styles.headerContainer}>
+          <Image source={{ uri: API_HOST + item.flag }} style={styles.flag} />
+          <Text style={styles.countryName}>{item.country}</Text>
+        </View>
+        <View style={styles.divider}></View>
+
+        <View style={styles.countryDetails}>
+          <View style={styles.durationContainer}>
+            <View style={styles.durationItem}>
+              {renderDurationIcon(item.slow11)}
+              <Text style={styles.visitDuration}>11 days</Text>
+            </View>
+            <View style={styles.durationItem}>
+              {renderDurationIcon(item.slow31)}
+              <Text style={styles.visitDuration}>31 days</Text>
+            </View>
+            <View style={styles.durationItem}>
+              {renderDurationIcon(item.slow101)}
+              <Text style={styles.visitDuration}>101 days</Text>
+            </View>
+          </View>
+
+          <View style={styles.btnContainer}>
+            {item.visited ? (
+              <TouchableOpacity onPress={() => openEditModal(item)} style={styles.editScoreBtn}>
+                <EditSvg width={14} height={14} />
+              </TouchableOpacity>
+            ) : null}
+
+            <TouchableOpacity
+              style={item.visited ? styles.visitedButton : styles.markVisitedButton}
+              onPress={() =>
+                updateSlow(
+                  item.country_id,
+                  item.visited === 1 ? false : true,
+                  Boolean(item.slow11),
+                  Boolean(item.slow31),
+                  Boolean(item.slow101)
+                )
+              }
+            >
+              {item.visited ? (
+                <View style={styles.visitedContainer}>
+                  <MarkIcon width={16} height={16} />
+                  <Text style={styles.visitedButtonText}>Visited</Text>
+                </View>
+              ) : (
+                <Text style={[styles.markVisitedButtonText]}>Mark visited</Text>
+              )}
+            </TouchableOpacity>
+          </View>
+        </View>
+      </View>
+    );
+  }
+);
+
+export default CountryItem;

+ 95 - 0
src/screens/InAppScreens/TravelsScreen/Components/CountryItem/styles.tsx

@@ -0,0 +1,95 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  countryItem: {
+    backgroundColor: '#FAFAFA',
+    padding: 12,
+    marginBottom: 16,
+    borderRadius: 8
+  },
+  headerContainer: { flexDirection: 'row', alignItems: 'center', gap: 12 },
+  flag: {
+    width: 48,
+    height: 48,
+    resizeMode: 'cover',
+    borderRadius: 24,
+    borderWidth: 1,
+    borderColor: Colors.DARK_LIGHT
+  },
+  countryDetails: {
+    flex: 1,
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center'
+  },
+  countryName: {
+    fontWeight: 'bold',
+    fontSize: 14,
+    color: Colors.DARK_BLUE
+  },
+  divider: { height: 1, backgroundColor: Colors.DARK_LIGHT, marginVertical: 12 },
+  visitDurationRow: {
+    flexDirection: 'row',
+    alignItems: 'center'
+  },
+  durationContainer: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    gap: 8
+  },
+  durationItem: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 4
+  },
+  durationIconActive: {
+    backgroundColor: Colors.WHITE
+  },
+  durationIconInactive: {
+    backgroundColor: Colors.LIGHT_GRAY
+  },
+  visitDuration: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    fontSize: 13
+  },
+  btnContainer: { alignItems: 'center', justifyContent: 'center', flexDirection: 'row', gap: 8 },
+  markVisitedButton: {
+    backgroundColor: Colors.ORANGE,
+    paddingVertical: 6,
+    paddingHorizontal: 12,
+    borderRadius: 6,
+    alignItems: 'center',
+    borderWidth: 1,
+    borderColor: Colors.ORANGE
+  },
+  visitedButton: {
+    backgroundColor: 'transparent',
+    paddingVertical: 6,
+    paddingHorizontal: 12,
+    borderRadius: 6,
+    alignItems: 'center',
+    borderColor: Colors.BORDER_LIGHT,
+    borderWidth: 1
+  },
+  markVisitedButtonText: {
+    color: 'white',
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  visitedButtonText: {
+    color: Colors.DARK_BLUE,
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  visitedContainer: { gap: 8, flexDirection: 'row', alignItems: 'center' },
+  editScoreBtn: {
+    borderRadius: 20,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY,
+    alignItems: 'center',
+    justifyContent: 'center',
+    padding: 7
+  }
+});

+ 120 - 0
src/screens/InAppScreens/TravelsScreen/Components/EditSlowModal/index.tsx

@@ -0,0 +1,120 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, TouchableOpacity } from 'react-native';
+import ReactModal from 'react-native-modal';
+
+import { Button, CheckBox } from 'src/components';
+import { ButtonVariants } from 'src/types/components';
+
+import { SlowData } from '../../utils/types';
+import { Colors } from 'src/theme';
+import { styles } from './styles';
+
+const EditModal = ({
+  isVisible,
+  onClose,
+  item,
+  updateSlow
+}: {
+  isVisible: boolean;
+  onClose: () => void;
+  item: SlowData;
+  updateSlow: (id: number, v: boolean, s11: boolean, s31: boolean, s101: boolean) => void;
+}) => {
+  const [isEnabled11, setIsEnabled11] = useState(Boolean(item.slow11));
+  const [isEnabled31, setIsEnabled31] = useState(Boolean(item.slow31));
+  const [isEnabled101, setIsEnabled101] = useState(Boolean(item.slow101));
+
+  useEffect(() => {
+    setIsEnabled11(Boolean(item.slow11));
+    setIsEnabled31(Boolean(item.slow31));
+    setIsEnabled101(Boolean(item.slow101));
+  }, [item, isVisible]);
+
+  return (
+    <ReactModal
+      isVisible={isVisible}
+      onBackdropPress={onClose}
+      style={styles.modal}
+      statusBarTranslucent={true}
+      presentationStyle="overFullScreen"
+    >
+      <View style={styles.modalContent}>
+        <View style={styles.optionsContainer}>
+          <TouchableOpacity
+            disabled={isEnabled31 || isEnabled101}
+            onPress={() => {
+              setIsEnabled11(!isEnabled11);
+              setIsEnabled31(isEnabled11 ? false : isEnabled31);
+            }}
+            style={styles.optionBtn}
+          >
+            <CheckBox
+              onChange={() => {
+                setIsEnabled11(!isEnabled11);
+                setIsEnabled31(isEnabled11 ? false : isEnabled31);
+              }}
+              value={isEnabled11}
+              color={'#0F3F4F'}
+              disabled={isEnabled31 || isEnabled101}
+            />
+            <Text style={styles.optionText}>11 days</Text>
+          </TouchableOpacity>
+          <TouchableOpacity
+            disabled={!isEnabled11 || isEnabled101}
+            onPress={() => {
+              setIsEnabled31(!isEnabled31);
+              setIsEnabled101(isEnabled31 ? false : isEnabled101);
+            }}
+            style={styles.optionBtn}
+          >
+            <CheckBox
+              onChange={() => {
+                setIsEnabled31(!isEnabled31);
+                setIsEnabled101(isEnabled31 ? false : isEnabled101);
+              }}
+              value={isEnabled31}
+              color={'#0F3F4F'}
+              disabled={!isEnabled11 || isEnabled101}
+            />
+            <Text style={styles.optionText}>31 days</Text>
+          </TouchableOpacity>
+          <TouchableOpacity
+            disabled={!isEnabled11 || !isEnabled31}
+            onPress={() => setIsEnabled101(!isEnabled101)}
+            style={styles.optionBtn}
+          >
+            <CheckBox
+              onChange={() => setIsEnabled101(!isEnabled101)}
+              value={isEnabled101}
+              color={'#0F3F4F'}
+              disabled={!isEnabled11 || !isEnabled31}
+            />
+            <Text style={styles.optionText}>101 days</Text>
+          </TouchableOpacity>
+        </View>
+        <Button
+          children="Done"
+          onPress={() => {
+            updateSlow(
+              item.country_id,
+              Boolean(item.visited),
+              isEnabled11,
+              isEnabled31,
+              isEnabled101
+            );
+            onClose();
+          }}
+        />
+        <Button
+          children="Close"
+          onPress={onClose}
+          variant={ButtonVariants.OPACITY}
+          containerStyles={styles.closeBtn}
+          textStyles={{ color: Colors.DARK_BLUE }}
+        />
+      </View>
+    </ReactModal>
+  );
+};
+
+export default EditModal;

+ 39 - 0
src/screens/InAppScreens/TravelsScreen/Components/EditSlowModal/styles.tsx

@@ -0,0 +1,39 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  modal: {
+    justifyContent: 'flex-end',
+    margin: 0
+  },
+  modalContent: {
+    backgroundColor: 'white',
+    borderRadius: 15,
+    paddingHorizontal: 16,
+    gap: 16,
+    paddingVertical: 24
+  },
+  optionsContainer: {
+    gap: 12,
+    marginBottom: 12
+  },
+  optionBtn: { flexDirection: 'row', alignItems: 'center', gap: 8 },
+  optionText: {
+    fontSize: 13,
+    fontWeight: 'bold',
+    marginVertical: 10
+  },
+  doneButton: {
+    backgroundColor: Colors.ORANGE,
+    padding: 12,
+    borderRadius: 4,
+    marginTop: 20,
+    alignItems: 'center'
+  },
+  doneButtonText: {
+    fontSize: 16,
+    color: 'white',
+    fontWeight: '600'
+  },
+  closeBtn: { backgroundColor: Colors.WHITE, borderColor: '#B7C6CB' }
+});

+ 204 - 0
src/screens/InAppScreens/TravelsScreen/CountriesScreen/index.tsx

@@ -0,0 +1,204 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { View, Text, TouchableOpacity, ScrollView, FlatList } from 'react-native';
+import ReactModal from 'react-native-modal';
+import * as Progress from 'react-native-progress';
+
+import CountryItem from '../Components/CountryItem';
+import EditModal from '../Components/EditSlowModal';
+import { Header, PageWrapper, WarningModal } from 'src/components';
+
+import { useGetSlowQuery, usePostSetSlowMutation } from '@api/countries';
+import { StoreType, storage } from 'src/storage';
+import { SlowData } from '../utils/types';
+import { Colors } from 'src/theme';
+import { styles } from './styles';
+
+import ChevronIcon from 'assets/icons/travels-screens/down-arrow.svg';
+import IngoSvg from 'assets/icons/travels-screens/info.svg';
+
+const CountriesScreen = () => {
+  const token = storage.get('token', StoreType.STRING) as string;
+  const { data } = useGetSlowQuery(token, true);
+  const { mutate: updateSlow } = usePostSetSlowMutation();
+  const [slow, setSlow] = useState<SlowData[] | null>(null);
+  const [megaSelectorVisible, setMegaSelectorVisible] = useState(false);
+  const [selectedMega, setSelectedMega] = useState<{ name: string; id: number }>({
+    name: 'ALL',
+    id: -1
+  });
+  const [total, setTotal] = useState({ slow: 0, visited: 0 });
+  const [isEditModalVisible, setIsEditModalVisible] = useState(false);
+  const [currentItem, setCurrentItem] = useState<SlowData | null>(null);
+  const [infoModalVisible, setInfoModalVisible] = useState(false);
+
+  const handleOpenEditModal = (item: SlowData) => {
+    setCurrentItem(item);
+    setIsEditModalVisible(true);
+  };
+
+  useEffect(() => {
+    if (slow && slow.length) {
+      calcTotalScore();
+    }
+  }, [slow]);
+
+  useEffect(() => {
+    if (data && data.result === 'OK') {
+      if (selectedMega.id === -1) {
+        setSlow(data?.slow);
+      } else {
+        setSlow(data?.slow?.filter((item) => item.mega.includes(selectedMega.id)));
+      }
+    }
+  }, [selectedMega, data]);
+
+  const calcTotalScore = () => {
+    let visited = 0;
+    let slow11 = 0;
+    let slow31 = 0;
+    let slow101 = 0;
+
+    slow?.forEach((item: SlowData) => {
+      visited += item.visited;
+      slow11 += item.slow11;
+      slow31 += item.slow31;
+      slow101 += item.slow101;
+    });
+
+    setTotal({ slow: slow11 + slow31 + slow101, visited });
+  };
+
+  const handleUpdateSlow = useCallback(
+    (id: number, v: boolean, s11: boolean, s31: boolean, s101: boolean) => {
+      const updatedSlow = slow?.map((item) => {
+        if (item.country_id === id) {
+          return {
+            ...item,
+            visited: Number(v) as 0 | 1,
+            slow11: Number(s11) as 0 | 1,
+            slow31: Number(s31) as 0 | 1,
+            slow101: Number(s101) as 0 | 1
+          };
+        }
+
+        return item;
+      });
+
+      const updatedSlowData = {
+        token,
+        id,
+        v,
+        s11,
+        s31,
+        s101
+      };
+
+      updateSlow(updatedSlowData);
+      updatedSlow && setSlow(updatedSlow);
+    },
+    [slow]
+  );
+
+  const renderCountryItem = ({ item }: { item: SlowData }) => (
+    <CountryItem item={item} updateSlow={handleUpdateSlow} openEditModal={handleOpenEditModal} />
+  );
+
+  return (
+    <PageWrapper>
+      <Header label="Countries" />
+      <TouchableOpacity style={styles.megaSelector} onPress={() => setMegaSelectorVisible(true)}>
+        <Text style={styles.megaButtonText}>{selectedMega.name}</Text>
+        <ChevronIcon width={18} height={18} />
+      </TouchableOpacity>
+
+      <View style={styles.progressHeader}>
+        <Text style={styles.visitedCountriesCount}>Visited countries</Text>
+        <Text style={styles.visitedCountriesCount}>
+          {slow?.length
+            ? `${total.visited}/${slow.length} • ${(total.visited * 100) / slow.length}%`
+            : '0/0 • 100%'}
+        </Text>
+      </View>
+
+      <Progress.Bar
+        progress={slow?.length ? total.visited / slow.length : 1}
+        width={null}
+        height={4}
+        color={Colors.DARK_BLUE}
+        borderWidth={0}
+        borderRadius={5}
+        unfilledColor={Colors.DARK_LIGHT}
+      />
+
+      <View style={styles.slowScoreSection}>
+        <Text style={styles.visitedCountriesCount}>
+          SLOW score: {slow?.length ? total.slow : 0}
+        </Text>
+        <TouchableOpacity style={styles.infoBtn} onPress={() => setInfoModalVisible(true)}>
+          <IngoSvg />
+        </TouchableOpacity>
+      </View>
+
+      {slow && (
+        <FlatList
+          data={slow}
+          renderItem={renderCountryItem}
+          keyExtractor={(item) => item.country_id.toString()}
+          showsVerticalScrollIndicator={false}
+          style={{ paddingTop: 8 }}
+        />
+      )}
+
+      <ReactModal
+        isVisible={megaSelectorVisible}
+        onBackdropPress={() => setMegaSelectorVisible(false)}
+        style={styles.modal}
+        statusBarTranslucent={true}
+        presentationStyle="overFullScreen"
+      >
+        <View style={styles.wrapper}>
+          <ScrollView style={{ paddingBottom: 16 }} showsVerticalScrollIndicator={false}>
+            <TouchableOpacity
+              style={styles.btnOption}
+              onPress={() => {
+                setMegaSelectorVisible(false);
+                setSelectedMega({ name: 'ALL', id: -1 });
+              }}
+            >
+              <Text style={styles.btnOptionText}>ALL</Text>
+            </TouchableOpacity>
+            {data?.megaregions?.map((mega) => (
+              <TouchableOpacity
+                key={mega.id}
+                style={styles.btnOption}
+                onPress={() => {
+                  setMegaSelectorVisible(false);
+                  setSelectedMega(mega);
+                }}
+              >
+                <Text style={styles.btnOptionText}>{mega.name}</Text>
+              </TouchableOpacity>
+            ))}
+          </ScrollView>
+        </View>
+      </ReactModal>
+      {currentItem && (
+        <EditModal
+          isVisible={isEditModalVisible}
+          onClose={() => setIsEditModalVisible(false)}
+          item={currentItem}
+          updateSlow={(id, v, s11, s31, s101) => handleUpdateSlow(id, v, s11, s31, s101)}
+        />
+      )}
+      <WarningModal
+        isVisible={infoModalVisible}
+        onClose={() => setInfoModalVisible(false)}
+        type="success"
+        title="SLOW score"
+        message={`Mark countries as visited.\nTell us if you stayed longer (7, 31, 101) days, this will increase your SLOW score, each longer stay should include at least 7 days countinuous stay in a country.`}
+      />
+    </PageWrapper>
+  );
+};
+
+export default CountriesScreen;

+ 57 - 0
src/screens/InAppScreens/TravelsScreen/CountriesScreen/styles.tsx

@@ -0,0 +1,57 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  btnOption: {
+    paddingHorizontal: 16,
+    paddingVertical: 9,
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 16
+  },
+  btnOptionText: { fontSize: 16, fontWeight: '600', color: Colors.DARK_BLUE },
+  wrapper: {
+    backgroundColor: Colors.WHITE,
+    padding: 16,
+    borderTopLeftRadius: 10,
+    borderTopRightRadius: 10,
+    height: '86%'
+  },
+  modal: {
+    justifyContent: 'flex-end',
+    margin: 0
+  },
+  megaSelector: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    borderRadius: 6,
+    backgroundColor: Colors.DARK_LIGHT,
+    justifyContent: 'space-between',
+    marginBottom: 16
+  },
+  megaButtonText: {
+    color: Colors.DARK_BLUE,
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  progressHeader: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    marginBottom: 8
+  },
+  visitedCountriesCount: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    fontSize: 12
+  },
+  slowScoreSection: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    paddingTop: 8
+  },
+  infoBtn: { paddingVertical: 8, paddingLeft: 16 }
+});

+ 8 - 1
src/screens/InAppScreens/TravelsScreen/index.tsx

@@ -22,7 +22,14 @@ const TravelsScreen = () => {
   const buttons: MenuButtonType[] = [
     {
       label: 'Countries',
-      icon: <FlagsIcon fill={Colors.DARK_BLUE} width={20} height={20} />
+      icon: <FlagsIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
+      buttonFn: (navigation) => {
+        if (!token) {
+          setIsModalVisible(true);
+        } else {
+          navigation.navigate(NAVIGATION_PAGES.COUNTRIES);
+        }
+      }
     },
     {
       label: 'Regions',

+ 12 - 0
src/screens/InAppScreens/TravelsScreen/utils/types.ts

@@ -77,3 +77,15 @@ export interface RegionAddData {
   name?: string;
   can_be_hidden?: boolean;
 }
+
+export interface SlowData {
+  country_id: number;
+  country: string;
+  flag: string;
+  mega: number[];
+  visited: 0 | 1;
+  slow11: 0 | 1;
+  slow31: 0 | 1;
+  slow101: 0 | 1;
+  yes: number;
+}

+ 8 - 3
src/types/api.ts

@@ -10,7 +10,8 @@ export enum API_ROUTE {
   STATISTICS = 'statistics',
   KYE = 'kye',
   PHOTOS = 'photos',
-  TRIPS = 'trips'
+  TRIPS = 'trips',
+  SLOW = 'slow'
 }
 
 export enum API_ENDPOINT {
@@ -57,7 +58,9 @@ export enum API_ENDPOINT {
   GET_TRIP = 'get-trip',
   SET_NEW_TRIP = 'new-trip',
   UPDATE_TRIP = 'update-trip',
-  DELETE_TRIP = 'delete-trip'
+  DELETE_TRIP = 'delete-trip',
+  GET_SLOW = 'get-slow-app',
+  SET_SLOW = 'set-slow'
 }
 
 export enum API {
@@ -103,7 +106,9 @@ export enum API {
   GET_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.GET_TRIP}`,
   SET_NEW_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.SET_NEW_TRIP}`,
   UPDATE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.UPDATE_TRIP}`,
-  DELETE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.DELETE_TRIP}`
+  DELETE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.DELETE_TRIP}`,
+  GET_SLOW = `${API_ROUTE.SLOW}/${API_ENDPOINT.GET_SLOW}`,
+  SET_SLOW = `${API_ROUTE.SLOW}/${API_ENDPOINT.SET_SLOW}`
 }
 
 export type BaseAxiosError = AxiosError;

+ 1 - 0
src/types/navigation.ts

@@ -31,4 +31,5 @@ export enum NAVIGATION_PAGES {
   TRIPS = 'inAppTrips',
   ADD_TRIP = 'inAppAddTrip',
   ADD_REGIONS = 'inAppAddRegions',
+  COUNTRIES = 'inAppCountries'
 }