Browse Source

fixed conflicts statistics

Viktoriia 1 year ago
parent
commit
ee6a67fa2b
100 changed files with 3087 additions and 208 deletions
  1. 23 2
      Route.tsx
  2. 3 1
      app.config.ts
  3. 0 0
      assets/geojson/kye.json
  4. 1 0
      assets/icons/travels-screens/add-img.svg
  5. 10 0
      assets/icons/travels-screens/calendar.svg
  6. 3 0
      assets/icons/travels-screens/check.svg
  7. 3 0
      assets/icons/travels-screens/chevron-bottom.svg
  8. 10 0
      assets/icons/travels-screens/chevron-right.svg
  9. 10 0
      assets/icons/travels-screens/choose.svg
  10. 10 0
      assets/icons/travels-screens/circle-check-regular.svg
  11. 10 0
      assets/icons/travels-screens/circle-check.svg
  12. 10 0
      assets/icons/travels-screens/circle-plus.svg
  13. 10 0
      assets/icons/travels-screens/down-arrow.svg
  14. 4 0
      assets/icons/travels-screens/info.svg
  15. 10 0
      assets/icons/travels-screens/pen-to-square.svg
  16. 10 0
      assets/icons/travels-screens/rotate.svg
  17. 10 0
      assets/icons/travels-screens/save.svg
  18. 3 0
      assets/icons/travels-screens/square.svg
  19. 12 0
      assets/icons/travels-screens/three-dots.svg
  20. 3 0
      assets/icons/travels-screens/trash-solid.svg
  21. 5 0
      assets/icons/travels-screens/trip.svg
  22. 12 0
      assets/icons/travels-screens/x-circle.svg
  23. 4 1
      package.json
  24. 93 0
      patches/react-native-calendars+1.1304.1.patch
  25. 7 2
      src/components/Button/index.tsx
  26. 2 1
      src/components/Calendars/RangeCalendar/Navigation/index.tsx
  27. 110 68
      src/components/Calendars/RangeCalendar/index.tsx
  28. 9 55
      src/components/Calendars/RangeCalendar/style.ts
  29. 38 22
      src/components/FlatList/index.tsx
  30. 38 16
      src/components/FlatList/item.tsx
  31. 13 1
      src/components/FlatList/styles.ts
  32. 4 2
      src/components/Input/index.tsx
  33. 13 5
      src/components/Modal/index.tsx
  34. 2 2
      src/components/Modal/style.ts
  35. 65 7
      src/components/WarningModal/index.tsx
  36. 2 1
      src/components/WarningModal/styles.tsx
  37. 1 1
      src/constants/secrets.ts
  38. 57 15
      src/db/index.ts
  39. 35 0
      src/modules/api/countries/countries-api.tsx
  40. 4 0
      src/modules/api/countries/countries-query-keys.tsx
  41. 3 0
      src/modules/api/countries/index.ts
  42. 2 0
      src/modules/api/countries/queries/index.ts
  43. 17 0
      src/modules/api/countries/queries/use-post-get-slow.tsx
  44. 17 0
      src/modules/api/countries/queries/use-post-set-slow.tsx
  45. 38 0
      src/modules/api/myDARE/dare-api.tsx
  46. 6 0
      src/modules/api/myDARE/dare-query-keys.tsx
  47. 3 0
      src/modules/api/myDARE/index.ts
  48. 3 0
      src/modules/api/myDARE/queries/index.ts
  49. 17 0
      src/modules/api/myDARE/queries/use-post-get-megaregions-dare.tsx
  50. 17 0
      src/modules/api/myDARE/queries/use-post-get-regions-dare.tsx
  51. 17 0
      src/modules/api/myDARE/queries/use-post-set-update-dare.tsx
  52. 3 0
      src/modules/api/myRegions/index.ts
  53. 4 0
      src/modules/api/myRegions/queries/index.ts
  54. 17 0
      src/modules/api/myRegions/queries/use-post-get-megaregions.tsx
  55. 17 0
      src/modules/api/myRegions/queries/use-post-get-regions-qe.tsx
  56. 17 0
      src/modules/api/myRegions/queries/use-post-set-update-nm.tsx
  57. 17 0
      src/modules/api/myRegions/queries/use-post-set-update-tcc.tsx
  58. 64 0
      src/modules/api/myRegions/regions-api.tsx
  59. 6 0
      src/modules/api/myRegions/regions-query-keys.tsx
  60. 3 0
      src/modules/api/photos/index.ts
  61. 112 0
      src/modules/api/photos/photos-api.tsx
  62. 9 0
      src/modules/api/photos/photos-query-keys.tsx
  63. 7 0
      src/modules/api/photos/queries/index.ts
  64. 22 0
      src/modules/api/photos/queries/use-post-delete-photo.tsx
  65. 17 0
      src/modules/api/photos/queries/use-post-get-photos-for-user.tsx
  66. 22 0
      src/modules/api/photos/queries/use-post-get-remove-temp.tsx
  67. 17 0
      src/modules/api/photos/queries/use-post-get-temp.tsx
  68. 21 0
      src/modules/api/photos/queries/use-post-set-save-temp.tsx
  69. 28 0
      src/modules/api/photos/queries/use-post-set-upload-temp.tsx
  70. 21 0
      src/modules/api/photos/queries/use-post-update-photo.tsx
  71. 3 0
      src/modules/api/travels/index.ts
  72. 2 0
      src/modules/api/travels/queries/index.ts
  73. 17 0
      src/modules/api/travels/queries/use-post-get-kye.tsx
  74. 21 0
      src/modules/api/travels/queries/use-post-set-kye.tsx
  75. 17 0
      src/modules/api/travels/travels-api.tsx
  76. 4 0
      src/modules/api/travels/travels-query-keys.tsx
  77. 3 0
      src/modules/api/trips/index.ts
  78. 7 0
      src/modules/api/trips/queries/index.ts
  79. 22 0
      src/modules/api/trips/queries/use-post-delete-trip.tsx
  80. 17 0
      src/modules/api/trips/queries/use-post-get-regions-for-trips.tsx
  81. 17 0
      src/modules/api/trips/queries/use-post-get-trip.tsx
  82. 17 0
      src/modules/api/trips/queries/use-post-get-trips-for-year.tsx
  83. 17 0
      src/modules/api/trips/queries/use-post-get-trips-years.tsx
  84. 16 0
      src/modules/api/trips/queries/use-post-set-new-trip.tsx
  85. 17 0
      src/modules/api/trips/queries/use-post-set-update-trip.tsx
  86. 95 0
      src/modules/api/trips/trips-api.tsx
  87. 9 0
      src/modules/api/trips/trips-query-keys.tsx
  88. 3 3
      src/screens/InAppScreens/MapScreen/index.tsx
  89. 3 3
      src/screens/InAppScreens/TravellersScreen/StatisticsScreen/index.tsx
  90. 359 0
      src/screens/InAppScreens/TravelsScreen/AddNewTripScreen/index.tsx
  91. 100 0
      src/screens/InAppScreens/TravelsScreen/AddNewTripScreen/styles.tsx
  92. 311 0
      src/screens/InAppScreens/TravelsScreen/AddPhotoScreen/index.tsx
  93. 40 0
      src/screens/InAppScreens/TravelsScreen/AddPhotoScreen/styles.tsx
  94. 282 0
      src/screens/InAppScreens/TravelsScreen/AddRegionsScreen/index.tsx
  95. 73 0
      src/screens/InAppScreens/TravelsScreen/AddRegionsScreen/styles.tsx
  96. 86 0
      src/screens/InAppScreens/TravelsScreen/Components/CountryItem/index.tsx
  97. 101 0
      src/screens/InAppScreens/TravelsScreen/Components/CountryItem/styles.tsx
  98. 36 0
      src/screens/InAppScreens/TravelsScreen/Components/CustomButton.tsx
  99. 120 0
      src/screens/InAppScreens/TravelsScreen/Components/EditSlowModal/index.tsx
  100. 39 0
      src/screens/InAppScreens/TravelsScreen/Components/EditSlowModal/styles.tsx

+ 23 - 2
Route.tsx

@@ -30,6 +30,17 @@ import StatisticsScreen from './src/screens/InAppScreens/TravellersScreen/Statis
 import StatisticsListScreen from './src/screens/InAppScreens/TravellersScreen/StatisticsListScreen';
 
 import SeriesScreen from 'src/screens/InAppScreens/TravelsScreen/Series';
+import { SeriesItemScreen } from 'src/screens/InAppScreens/TravelsScreen/SeriesItemScreen';
+import EarthScreen from 'src/screens/InAppScreens/TravelsScreen/EarthScreen';
+import PhotosScreen from 'src/screens/InAppScreens/TravelsScreen/PhotosScreen';
+import MorePhotosScreen from 'src/screens/InAppScreens/TravelsScreen/MorePhotosScreen';
+import AddPhotoScreen from 'src/screens/InAppScreens/TravelsScreen/AddPhotoScreen';
+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 RegionsScreen from 'src/screens/InAppScreens/TravelsScreen/RegionsScreen';
+import DareScreen from 'src/screens/InAppScreens/TravelsScreen/DareScreen';
 
 import { NAVIGATION_PAGES } from './src/types';
 import { storage, StoreType } from './src/storage';
@@ -38,7 +49,6 @@ import { openDatabases } from './src/db';
 import TabBarButton from './src/components/TabBarButton';
 import { ParamListBase, RouteProp } from '@react-navigation/native';
 import setupDatabaseAndSync from 'src/database';
-import { SeriesItemScreen } from 'src/screens/InAppScreens/TravelsScreen/SeriesItemScreen';
 
 const ScreenStack = createStackNavigator();
 const BottomTab = createBottomTabNavigator();
@@ -96,7 +106,8 @@ const Route = () => {
         android: {
           height: 58
         }
-      })
+      }),
+      backgroundColor: '#FAFAFA',
     }
   });
 
@@ -169,6 +180,16 @@ const Route = () => {
                     name={NAVIGATION_PAGES.SERIES_ITEM}
                     component={SeriesItemScreen}
                   />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.EARTH} component={EarthScreen} />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.PHOTOS} component={PhotosScreen} />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.MORE_PHOTOS} component={MorePhotosScreen} />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_PHOTO} component={AddPhotoScreen} />
+                  <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.Screen name={NAVIGATION_PAGES.REGIONS} component={RegionsScreen} />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.DARE} component={DareScreen} />
                 </ScreenStack.Navigator>
               )}
             </BottomTab.Screen>

+ 3 - 1
app.config.ts

@@ -6,6 +6,7 @@ import dotenv from 'dotenv';
 import type { ConfigContext, ExpoConfig } from 'expo/config';
 
 const API_HOST = env.ENV === 'production' ? env.PRODUCTION_API_HOST : env.DEVELOPMENT_API_HOST;
+const MAP_HOST = env.ENV === 'production' ? env.PRODUCTION_MAP_HOST : env.DEVELOPMENT_MAP_HOST;
 
 dotenv.config({
   path: path.resolve(process.cwd(), '.env')
@@ -20,13 +21,14 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
   // Should be updated after every production release (deploy to AppStore/PlayMarket)
   version: '1.0.0',
   // Should be updated after every dependency change
-  runtimeVersion: '1.2',
+  runtimeVersion: '1.3',
   orientation: 'portrait',
   icon: './assets/icon.png',
   userInterfaceStyle: 'light',
   extra: {
     ENV: env.ENV,
     API_HOST: API_HOST,
+    MAP_HOST: MAP_HOST,
     eas: {
       projectId: env.EAS_PROJECT_ID
     }

File diff suppressed because it is too large
+ 0 - 0
assets/geojson/kye.json


File diff suppressed because it is too large
+ 1 - 0
assets/icons/travels-screens/add-img.svg


+ 10 - 0
assets/icons/travels-screens/calendar.svg

@@ -0,0 +1,10 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2039_19282)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.5 2.00195C9.05228 2.00195 9.5 2.44967 9.5 3.00195V4.00195H14.5V3.00195C14.5 2.44967 14.9477 2.00195 15.5 2.00195C16.0523 2.00195 16.5 2.44967 16.5 3.00195V4.00195H18.5C20.1569 4.00195 21.5 5.3451 21.5 7.00195V18.002C21.5 19.6588 20.1569 21.002 18.5 21.002H5.5C3.84315 21.002 2.5 19.6588 2.5 18.002V7.00195C2.5 5.3451 3.84315 4.00195 5.5 4.00195H7.5V3.00195C7.5 2.44967 7.94772 2.00195 8.5 2.00195ZM7.5 6.00195H5.5C4.94772 6.00195 4.5 6.44967 4.5 7.00195V9.50195H19.5V7.00195C19.5 6.44967 19.0523 6.00195 18.5 6.00195H16.5V7.00195C16.5 7.55424 16.0523 8.00195 15.5 8.00195C14.9477 8.00195 14.5 7.55424 14.5 7.00195V6.00195H9.5V7.00195C9.5 7.55424 9.05228 8.00195 8.5 8.00195C7.94772 8.00195 7.5 7.55424 7.5 7.00195V6.00195ZM19.5 11.502H4.5V18.002C4.5 18.5542 4.94772 19.002 5.5 19.002H18.5C19.0523 19.002 19.5 18.5542 19.5 18.002V11.502Z"/>
+</g>
+<defs>
+<clipPath id="clip0_2039_19282">
+<rect width="24" height="24" fill="white" transform="translate(0 0.00195312)"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
assets/icons/travels-screens/check.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10.07 2.87008C10.2522 3.05233 10.2522 3.3478 10.07 3.53005L4.93662 8.66338C4.75438 8.84563 4.4589 8.84563 4.27666 8.66338L1.94332 6.33005C1.76108 6.1478 1.76108 5.85233 1.94332 5.67008C2.12557 5.48784 2.42105 5.48784 2.60329 5.67008L4.60664 7.67343L9.40999 2.87008C9.59224 2.68784 9.88771 2.68784 10.07 2.87008Z" fill="white"/>
+</svg>

+ 3 - 0
assets/icons/travels-screens/chevron-bottom.svg

@@ -0,0 +1,3 @@
+<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5.49805 7.50098L10.4985 12.5014L15.4989 7.50098" stroke="#C8C8C8" stroke-width="1.66678" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 10 - 0
assets/icons/travels-screens/chevron-right.svg

@@ -0,0 +1,10 @@
+<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1693_17594)">
+<path d="M3.4154 1.5L6.70833 4.79293C7.09722 5.18182 7.09722 5.81818 6.70833 6.20707L3.4154 9.5" stroke="#0F3F4F" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+<defs>
+<clipPath id="clip0_1693_17594">
+<rect width="10" height="10" fill="white" transform="translate(0 0.501953)"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
assets/icons/travels-screens/choose.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_1730_17157)">
+<path d="M8 1.5C9.72391 1.5 11.3772 2.18482 12.5962 3.40381C13.8152 4.62279 14.5 6.27609 14.5 8C14.5 9.72391 13.8152 11.3772 12.5962 12.5962C11.3772 13.8152 9.72391 14.5 8 14.5C6.27609 14.5 4.62279 13.8152 3.40381 12.5962C2.18482 11.3772 1.5 9.72391 1.5 8C1.5 6.27609 2.18482 4.62279 3.40381 3.40381C4.62279 2.18482 6.27609 1.5 8 1.5ZM8 16C10.1217 16 12.1566 15.1571 13.6569 13.6569C15.1571 12.1566 16 10.1217 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34315C12.1566 0.842855 10.1217 0 8 0C5.87827 0 3.84344 0.842855 2.34315 2.34315C0.842855 3.84344 0 5.87827 0 8C0 10.1217 0.842855 12.1566 2.34315 13.6569C3.84344 15.1571 5.87827 16 8 16ZM11.5312 6.53125C11.825 6.2375 11.825 5.7625 11.5312 5.47188C11.2375 5.18125 10.7625 5.17813 10.4719 5.47188L7.00313 8.94063L5.53438 7.47188C5.24063 7.17813 4.76562 7.17813 4.475 7.47188C4.18437 7.76562 4.18125 8.24063 4.475 8.53125L6.475 10.5312C6.76875 10.825 7.24375 10.825 7.53438 10.5312L11.5312 6.53125Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_1730_17157">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 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>

+ 10 - 0
assets/icons/travels-screens/circle-check.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_1740_17429)">
+<path d="M8 16C10.1217 16 12.1566 15.1571 13.6569 13.6569C15.1571 12.1566 16 10.1217 16 8C16 5.87827 15.1571 3.84344 13.6569 2.34315C12.1566 0.842855 10.1217 0 8 0C5.87827 0 3.84344 0.842855 2.34315 2.34315C0.842855 3.84344 0 5.87827 0 8C0 10.1217 0.842855 12.1566 2.34315 13.6569C3.84344 15.1571 5.87827 16 8 16ZM11.5312 6.53125L7.53125 10.5312C7.2375 10.825 6.7625 10.825 6.47188 10.5312L4.47188 8.53125C4.17813 8.2375 4.17813 7.7625 4.47188 7.47188C4.76562 7.18125 5.24062 7.17813 5.53125 7.47188L7 8.94063L10.4688 5.46875C10.7625 5.175 11.2375 5.175 11.5281 5.46875C11.8187 5.7625 11.8219 6.2375 11.5281 6.52812L11.5312 6.53125Z"/>
+</g>
+<defs>
+<clipPath id="clip0_1740_17429">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

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

@@ -0,0 +1,10 @@
+<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1967_17639)">
+<path d="M8.25 16.001C10.3717 16.001 12.4066 15.1581 13.9069 13.6578C15.4071 12.1575 16.25 10.1227 16.25 8.00098C16.25 5.87924 15.4071 3.84441 13.9069 2.34412C12.4066 0.843831 10.3717 0.000976563 8.25 0.000976562C6.12827 0.000976563 4.09344 0.843831 2.59315 2.34412C1.09285 3.84441 0.25 5.87924 0.25 8.00098C0.25 10.1227 1.09285 12.1575 2.59315 13.6578C4.09344 15.1581 6.12827 16.001 8.25 16.001ZM7.5 10.751V8.75098H5.5C5.08437 8.75098 4.75 8.4166 4.75 8.00098C4.75 7.58535 5.08437 7.25098 5.5 7.25098H7.5V5.25098C7.5 4.83535 7.83437 4.50098 8.25 4.50098C8.66562 4.50098 9 4.83535 9 5.25098V7.25098H11C11.4156 7.25098 11.75 7.58535 11.75 8.00098C11.75 8.4166 11.4156 8.75098 11 8.75098H9V10.751C9 11.1666 8.66562 11.501 8.25 11.501C7.83437 11.501 7.5 11.1666 7.5 10.751Z" fill="white"/>
+</g>
+<defs>
+<clipPath id="clip0_1967_17639">
+<rect width="16" height="16" fill="white" transform="translate(0.25 0.000976562)"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
assets/icons/travels-screens/down-arrow.svg

@@ -0,0 +1,10 @@
+<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2039_18224)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1772 5.46104C14.4762 5.8348 14.4156 6.38019 14.0419 6.67919L9.04189 10.6792C8.72537 10.9324 8.27561 10.9324 7.95908 10.6792L2.95909 6.67919C2.58533 6.38019 2.52473 5.8348 2.82373 5.46104C3.12274 5.08728 3.66813 5.02668 4.04189 5.32569L8.50049 8.89257L12.9591 5.32569C13.3328 5.02668 13.8782 5.08728 14.1772 5.46104Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_2039_18224">
+<rect width="16" height="16" fill="white" transform="translate(0.5 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>

+ 10 - 0
assets/icons/travels-screens/pen-to-square.svg

@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1699_18642)">
+<path d="M16.5797 0.762988C15.8098 -0.0069336 14.5652 -0.0069336 13.7953 0.762988L12.7371 1.81768L16.1789 5.25947L17.2371 4.20127C18.007 3.43135 18.007 2.18682 17.2371 1.41689L16.5797 0.762988ZM6.06094 8.49736C5.84648 8.71182 5.68125 8.97549 5.58633 9.26729L4.5457 12.3892C4.44375 12.6915 4.52461 13.0255 4.74961 13.254C4.97461 13.4825 5.30859 13.5599 5.61445 13.4579L8.73633 12.4173C9.02461 12.3224 9.28828 12.1571 9.50625 11.9427L15.3879 6.05752L11.9426 2.61221L6.06094 8.49736ZM3.375 2.2501C1.51172 2.2501 0 3.76182 0 5.6251V14.6251C0 16.4884 1.51172 18.0001 3.375 18.0001H12.375C14.2383 18.0001 15.75 16.4884 15.75 14.6251V11.2501C15.75 10.6278 15.2473 10.1251 14.625 10.1251C14.0027 10.1251 13.5 10.6278 13.5 11.2501V14.6251C13.5 15.2474 12.9973 15.7501 12.375 15.7501H3.375C2.75273 15.7501 2.25 15.2474 2.25 14.6251V5.6251C2.25 5.00283 2.75273 4.5001 3.375 4.5001H6.75C7.37227 4.5001 7.875 3.99736 7.875 3.3751C7.875 2.75283 7.37227 2.2501 6.75 2.2501H3.375Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_1699_18642">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
assets/icons/travels-screens/rotate.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_2133_19451)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4.73572 4.47246C5.59286 3.64964 6.74826 3.14612 8.02131 3.14612C10.506 3.14612 12.5232 5.16243 12.7715 7.5389L12.6166 7.41952C12.2816 7.16139 11.8036 7.22739 11.549 7.56693C11.2943 7.90647 11.3594 8.39097 11.6944 8.6491L13.004 9.65833L13.4441 9.99741L13.8986 9.67866L15.3378 8.66943C15.6839 8.42677 15.7703 7.9457 15.5309 7.59494C15.2916 7.24418 14.817 7.15654 14.4709 7.3992L14.3005 7.5187C14.0399 4.32281 11.3637 1.60156 8.02131 1.60156C6.34316 1.60156 4.81679 2.26735 3.68792 3.35101C3.3824 3.64429 3.36929 4.13309 3.65863 4.44277C3.94797 4.75245 4.4302 4.76575 4.73572 4.47246ZM11.2636 11.5084C10.4065 12.3313 9.25109 12.8348 7.97804 12.8348C5.49334 12.8348 3.47613 10.8185 3.22785 8.442L3.38277 8.56138C3.71774 8.81951 4.19574 8.75351 4.4504 8.41397C4.70506 8.07444 4.63995 7.58993 4.30498 7.3318L2.9953 6.32257L2.55529 5.98349L2.10073 6.30224L0.661508 7.31147C0.315461 7.55413 0.229003 8.0352 0.468402 8.38596C0.707799 8.73672 1.1824 8.82436 1.52844 8.5817L1.69886 8.4622C1.95941 11.6581 4.63569 14.3793 7.97804 14.3793C9.65619 14.3793 11.1826 13.7136 12.3114 12.6299C12.6169 12.3366 12.6301 11.8478 12.3407 11.5381C12.0514 11.2284 11.5692 11.2152 11.2636 11.5084Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_2133_19451">
+<rect width="16" height="16" fill="white" transform="translate(0 0.00195312)"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
assets/icons/travels-screens/save.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_1937_23992)">
+<path d="M2.28571 0C1.025 0 0 1.025 0 2.28571V13.7143C0 14.975 1.025 16 2.28571 16H13.7143C14.975 16 16 14.975 16 13.7143V5.04643C16 4.43929 15.7607 3.85714 15.3321 3.42857L12.5714 0.667857C12.1429 0.239286 11.5607 0 10.9536 0H2.28571ZM2.28571 3.42857C2.28571 2.79643 2.79643 2.28571 3.42857 2.28571H10.2857C10.9179 2.28571 11.4286 2.79643 11.4286 3.42857V5.71429C11.4286 6.34643 10.9179 6.85714 10.2857 6.85714H3.42857C2.79643 6.85714 2.28571 6.34643 2.28571 5.71429V3.42857ZM8 9.14286C8.60621 9.14286 9.18759 9.38367 9.61624 9.81233C10.0449 10.241 10.2857 10.8224 10.2857 11.4286C10.2857 12.0348 10.0449 12.6162 9.61624 13.0448C9.18759 13.4735 8.60621 13.7143 8 13.7143C7.39379 13.7143 6.81241 13.4735 6.38376 13.0448C5.9551 12.6162 5.71429 12.0348 5.71429 11.4286C5.71429 10.8224 5.9551 10.241 6.38376 9.81233C6.81241 9.38367 7.39379 9.14286 8 9.14286Z"/>
+</g>
+<defs>
+<clipPath id="clip0_1937_23992">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
assets/icons/travels-screens/square.svg

@@ -0,0 +1,3 @@
+<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="1.25" y="0.5" width="15" height="15" rx="3.5" stroke="white"/>
+</svg>

+ 12 - 0
assets/icons/travels-screens/three-dots.svg

@@ -0,0 +1,12 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1696_18021)">
+<path d="M11.5 5C11.5 5.82843 10.8284 6.5 10 6.5C9.17157 6.5 8.5 5.82843 8.5 5C8.5 4.17157 9.17157 3.5 10 3.5C10.8284 3.5 11.5 4.17157 11.5 5Z" fill="white"/>
+<path d="M11.5 10C11.5 10.8284 10.8284 11.5 10 11.5C9.17157 11.5 8.5 10.8284 8.5 10C8.5 9.17157 9.17157 8.5 10 8.5C10.8284 8.5 11.5 9.17157 11.5 10Z" fill="white"/>
+<path d="M10 16.5C10.8284 16.5 11.5 15.8284 11.5 15C11.5 14.1716 10.8284 13.5 10 13.5C9.17157 13.5 8.5 14.1716 8.5 15C8.5 15.8284 9.17157 16.5 10 16.5Z" fill="white"/>
+</g>
+<defs>
+<clipPath id="clip0_1696_18021">
+<rect width="20" height="20" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
assets/icons/travels-screens/trash-solid.svg

@@ -0,0 +1,3 @@
+<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.725 0.553125C4.89375 0.2125 5.24062 0 5.61875 0H9.38125C9.75938 0 10.1062 0.2125 10.275 0.553125L10.5 1H13.5C14.0531 1 14.5 1.44687 14.5 2C14.5 2.55312 14.0531 3 13.5 3H1.5C0.946875 3 0.5 2.55312 0.5 2C0.5 1.44687 0.946875 1 1.5 1H4.5L4.725 0.553125ZM1.5 4H13.5V14C13.5 15.1031 12.6031 16 11.5 16H3.5C2.39688 16 1.5 15.1031 1.5 14V4ZM4.5 6C4.225 6 4 6.225 4 6.5V13.5C4 13.775 4.225 14 4.5 14C4.775 14 5 13.775 5 13.5V6.5C5 6.225 4.775 6 4.5 6ZM7.5 6C7.225 6 7 6.225 7 6.5V13.5C7 13.775 7.225 14 7.5 14C7.775 14 8 13.775 8 13.5V6.5C8 6.225 7.775 6 7.5 6ZM10.5 6C10.225 6 10 6.225 10 6.5V13.5C10 13.775 10.225 14 10.5 14C10.775 14 11 13.775 11 13.5V6.5C11 6.225 10.775 6 10.5 6Z"/>
+</svg>

+ 5 - 0
assets/icons/travels-screens/trip.svg

@@ -0,0 +1,5 @@
+<svg width="129" height="129" viewBox="0 0 129 129" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M23.6707 54.1246C29.1815 47.2282 41.7497 30.5135 41.7497 21.1249C41.7497 9.73822 32.5115 0.5 21.1249 0.5C9.73822 0.5 0.5 9.73822 0.5 21.1249C0.5 30.5135 13.0683 47.2282 18.579 54.1246C19.9003 55.7682 22.3495 55.7682 23.6707 54.1246ZM21.1249 14.2499C22.028 14.2495 22.9223 14.427 23.7568 14.7722C24.5914 15.1175 25.3496 15.6239 25.9884 16.2623C26.6272 16.9008 27.1339 17.6588 27.4796 18.4931C27.8253 19.3275 28.0033 20.2217 28.0033 21.1249C28.0033 22.028 27.8253 22.9223 27.4796 23.7566C27.1339 24.5909 26.6272 25.349 25.9884 25.9874C25.3496 26.6258 24.5914 27.1322 23.7568 27.4775C22.9223 27.8228 22.028 28.0003 21.1249 27.9998C20.2217 28.0003 19.3274 27.8228 18.4929 27.4775C17.6584 27.1322 16.9001 26.6258 16.2613 25.9874C15.6226 25.349 15.1158 24.5909 14.7701 23.7566C14.4244 22.9223 14.2465 22.028 14.2465 21.1249C14.2465 20.2217 14.4244 19.3275 14.7701 18.4931C15.1158 17.6588 15.6226 16.9008 16.2613 16.2623C16.9001 15.6239 17.6584 15.1175 18.4929 14.7722C19.3274 14.427 20.2217 14.2495 21.1249 14.2499Z"/>
+<path d="M24.4278 128.187C13.6643 126.381 4.76975 118.39 1.4784 107.57C0.738501 105.138 0.659679 104.421 0.637477 99.9223C0.616417 95.6403 0.707153 94.6138 1.29868 92.4503C3.82765 83.2006 10.8336 75.5715 19.5906 72.5314C24.2527 70.9129 23.4075 70.9601 51.1551 70.7685L77.0278 70.5898L78.32 69.8999C80.3116 68.8365 81.8917 67.2435 82.8101 65.3733C83.938 63.0765 83.9886 59.6558 82.9296 57.2883C82.0975 55.4282 79.8473 53.2171 77.8453 52.2922C76.4361 51.6412 76.2784 51.6359 55.437 51.5516L34.448 51.4666L36.0717 49.4457C37.9505 47.1073 41.1108 42.3535 42.1653 40.2796L42.8919 38.8506H59.987C76.1394 38.8506 77.2132 38.884 79.4617 39.4571C85.9809 41.1185 90.9628 45.0459 94.0387 50.9486C95.7663 54.2637 96.4932 57.2619 96.5031 61.1132C96.5134 65.118 95.9177 67.664 94.1094 71.3419C92.9261 73.7487 92.2741 74.6294 89.9804 76.9196C87.0381 79.8575 84.4318 81.4503 80.3371 82.8133L78.2312 83.5143L52.0576 83.6707C25.9976 83.8264 25.8764 83.8299 24.1293 84.4899C19.1357 86.3763 15.7761 89.7577 14.0579 94.6268C13.0889 97.3728 12.9851 101.607 13.825 104.134C14.7299 106.857 16.0671 109.104 17.8543 110.905C19.7664 112.833 21.3202 113.822 24.1934 114.941L26.1849 115.717L54.5409 115.802L82.897 115.888L83.9856 118.049C85.3366 120.732 87.0447 123.482 89.1237 126.322L90.7206 128.503L58.3023 128.467C40.4722 128.448 25.2287 128.322 24.4278 128.187Z"/>
+<path d="M110.421 127.269C115.931 120.373 128.5 103.658 128.5 94.2694C128.5 82.8828 119.262 73.6445 107.875 73.6445C96.4882 73.6445 87.25 82.8828 87.25 94.2694C87.25 103.658 99.8183 120.373 105.329 127.269C106.65 128.913 109.099 128.913 110.421 127.269ZM107.875 87.3944C108.778 87.3942 109.672 87.5718 110.506 87.9172C111.341 88.2626 112.099 88.7689 112.737 89.4074C113.376 90.0458 113.883 90.8038 114.228 91.638C114.574 92.4722 114.752 93.3664 114.752 94.2694C114.752 95.1724 114.574 96.0665 114.228 96.9008C113.883 97.735 113.376 98.493 112.737 99.1314C112.099 99.7698 111.341 100.276 110.506 100.622C109.672 100.967 108.778 101.145 107.875 101.144C106.972 101.145 106.078 100.967 105.243 100.622C104.409 100.276 103.651 99.7698 103.012 99.1314C102.374 98.493 101.867 97.735 101.521 96.9008C101.176 96.0665 100.998 95.1724 100.998 94.2694C100.998 93.3664 101.176 92.4722 101.521 91.638C101.867 90.8038 102.374 90.0458 103.012 89.4074C103.651 88.7689 104.409 88.2626 105.243 87.9172C106.078 87.5718 106.972 87.3942 107.875 87.3944Z"/>
+</svg>

+ 12 - 0
assets/icons/travels-screens/x-circle.svg

@@ -0,0 +1,12 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_1937_24043)">
+<rect y="0.00195312" width="24" height="24" rx="12" fill="white"/>
+<path d="M12 24.002C15.1826 24.002 18.2348 22.7377 20.4853 20.4872C22.7357 18.2368 24 15.1846 24 12.002C24 8.81936 22.7357 5.76711 20.4853 3.51667C18.2348 1.26624 15.1826 0.00195313 12 0.00195312C8.8174 0.00195313 5.76516 1.26624 3.51472 3.51667C1.26428 5.76711 0 8.81936 0 12.002C0 15.1846 1.26428 18.2368 3.51472 20.4872C5.76516 22.7377 8.8174 24.002 12 24.002ZM8.20312 8.20508C8.64375 7.76445 9.35625 7.76445 9.79219 8.20508L11.9953 10.4082L14.1984 8.20508C14.6391 7.76445 15.3516 7.76445 15.7875 8.20508C16.2234 8.6457 16.2281 9.3582 15.7875 9.79414L13.5844 11.9973L15.7875 14.2004C16.2281 14.641 16.2281 15.3535 15.7875 15.7895C15.3469 16.2254 14.6344 16.2301 14.1984 15.7895L11.9953 13.5863L9.79219 15.7895C9.35156 16.2301 8.63906 16.2301 8.20312 15.7895C7.76719 15.3488 7.7625 14.6363 8.20312 14.2004L10.4062 11.9973L8.20312 9.79414C7.7625 9.35352 7.7625 8.64102 8.20312 8.20508Z" fill="#0F3F4F"/>
+</g>
+<rect x="0.5" y="0.501953" width="23" height="23" rx="11.5" stroke="white"/>
+<defs>
+<clipPath id="clip0_1937_24043">
+<rect y="0.00195312" width="24" height="24" rx="12" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 4 - 1
package.json

@@ -39,15 +39,18 @@
     "patch-package": "^8.0.0",
     "postinstall-postinstall": "^2.1.0",
     "react": "18.2.0",
-    "react-native": "0.72.6",
+    "react-native": "0.72.10",
     "react-native-calendar-picker": "^7.1.4",
+    "react-native-calendars": "^1.1304.1",
     "react-native-device-detection": "^0.2.1",
     "react-native-gesture-handler": "~2.12.0",
+    "react-native-image-viewing": "^0.2.2",
     "react-native-keyboard-aware-scroll-view": "^0.9.5",
     "react-native-maps": "1.7.1",
     "react-native-mmkv": "^2.11.0",
     "react-native-modal": "^13.0.1",
     "react-native-pager-view": "6.2.0",
+    "react-native-progress": "^5.0.1",
     "react-native-reanimated": "~3.3.0",
     "react-native-render-html": "^6.3.4",
     "react-native-safe-area-context": "4.6.3",

+ 93 - 0
patches/react-native-calendars+1.1304.1.patch

@@ -0,0 +1,93 @@
+diff --git a/node_modules/react-native-calendars/src/calendar/day/period/index.js b/node_modules/react-native-calendars/src/calendar/day/period/index.js
+index f27db0e..434f414 100644
+--- a/node_modules/react-native-calendars/src/calendar/day/period/index.js
++++ b/node_modules/react-native-calendars/src/calendar/day/period/index.js
+@@ -87,21 +87,19 @@ const PeriodDay = (props) => {
+         return textStyle;
+     }, [marking, state]);
+     const fillerStyles = useMemo(() => {
+-        const leftFillerStyle = { backgroundColor: undefined };
+-        const rightFillerStyle = { backgroundColor: undefined };
++        const leftFillerStyle = { backgroundColor: marking?.disabled ? 'transparent' : 'rgb(250, 223, 194)' };
++        const rightFillerStyle = { backgroundColor: marking?.disabled ? 'transparent' : 'rgb(250, 223, 194)' };
+         let fillerStyle = {};
+         const start = markingStyle.startingDay;
+         const end = markingStyle.endingDay;
+-        if (start && !end) {
+-            rightFillerStyle.backgroundColor = markingStyle.startingDay?.backgroundColor;
++        if (start) {
++          leftFillerStyle.backgroundColor = markingStyle.day?.backgroundColor;
+         }
+-        else if (end && !start) {
+-            leftFillerStyle.backgroundColor = markingStyle.endingDay?.backgroundColor;
++        if (end) {
++          rightFillerStyle.backgroundColor = markingStyle.day?.backgroundColor;
+         }
+-        else if (markingStyle.day) {
+-            leftFillerStyle.backgroundColor = markingStyle.day?.backgroundColor;
+-            rightFillerStyle.backgroundColor = markingStyle.day?.backgroundColor;
+-            fillerStyle = { backgroundColor: markingStyle.day?.backgroundColor };
++        if (!start && !end && markingStyle.day) {
++          fillerStyle = { backgroundColor: markingStyle.day?.backgroundColor };
+         }
+         return { leftFillerStyle, rightFillerStyle, fillerStyle };
+     }, [marking]);
+diff --git a/node_modules/react-native-calendars/src/calendar/day/period/style.js b/node_modules/react-native-calendars/src/calendar/day/period/style.js
+index 51e2082..d82f5eb 100644
+--- a/node_modules/react-native-calendars/src/calendar/day/period/style.js
++++ b/node_modules/react-native-calendars/src/calendar/day/period/style.js
+@@ -10,7 +10,7 @@ export default function styleConstructor(theme = {}) {
+             marginLeft: -1
+         },
+         base: {
+-            width: 38,
++            width: FILLER_HEIGHT,
+             height: FILLER_HEIGHT,
+             alignItems: 'center',
+             justifyContent: 'center'
+@@ -44,7 +44,10 @@ export default function styleConstructor(theme = {}) {
+             bottom: 3
+         },
+         today: {
+-            backgroundColor: appStyle.todayBackgroundColor
++            backgroundColor: appStyle.todayBackgroundColor,
++            borderColor: '#ED9334',
++            borderWidth: 1,
++            borderRadius: 17
+         },
+         todayText: {
+             fontWeight: '500',
+diff --git a/node_modules/react-native-calendars/src/calendar/index.js b/node_modules/react-native-calendars/src/calendar/index.js
+index 6f46e8a..de8ce86 100644
+--- a/node_modules/react-native-calendars/src/calendar/index.js
++++ b/node_modules/react-native-calendars/src/calendar/index.js
+@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
+ import XDate from 'xdate';
+ import isEmpty from 'lodash/isEmpty';
+ import React, { useRef, useState, useEffect, useCallback, useMemo } from 'react';
+-import { View } from 'react-native';
++import { View, PanResponder } from 'react-native';
+ // @ts-expect-error
+ import GestureRecognizer, { swipeDirections } from 'react-native-swipe-gestures';
+ import constants from '../commons/constants';
+@@ -137,6 +137,11 @@ const Calendar = (props) => {
+         }
+         return false;
+     }, [currentMonth, displayLoadingIndicator, markedDates]);
++    const panResponder = useRef(
++      PanResponder.create({
++        onStartShouldSetPanResponder: () => true,
++      })
++    ).current;
+     const renderHeader = () => {
+         const headerProps = extractHeaderProps(props);
+         const ref = customHeader ? undefined : header;
+@@ -150,7 +155,7 @@ const Calendar = (props) => {
+     };
+     const gestureProps = enableSwipeMonths ? swipeProps : undefined;
+     return (<GestureComponent {...gestureProps}>
+-      <View style={[style.current.container, propsStyle]} testID={testID} accessibilityElementsHidden={accessibilityElementsHidden} // iOS
++      <View {...panResponder.panHandlers} style={[style.current.container, propsStyle]} testID={testID} accessibilityElementsHidden={accessibilityElementsHidden} // iOS
+      importantForAccessibility={importantForAccessibility} // Android
+     >
+         {renderHeader()}

+ 7 - 2
src/components/Button/index.tsx

@@ -10,9 +10,10 @@ type Props = {
   onPress?: () => void;
   containerStyles?: CSSProperties;
   textStyles?: CSSProperties;
+  disabled?: boolean;
 };
 
-export const Button: FC<Props> = ({ children, variant, onPress, containerStyles, textStyles }) => {
+export const Button: FC<Props> = ({ children, variant, onPress, containerStyles, textStyles, disabled }) => {
   return (
     <>
       {variant === ButtonVariants.OPACITY ? (
@@ -21,6 +22,7 @@ export const Button: FC<Props> = ({ children, variant, onPress, containerStyles,
           children={children}
           containerStyles={containerStyles}
           textStyles={textStyles}
+          disabled={disabled}
         />
       ) : variant === ButtonVariants.FILL ? (
         <FillButton
@@ -43,17 +45,20 @@ type VariantProps = {
   onPress?: () => void;
   containerStyles?: CSSProperties | {};
   textStyles?: CSSProperties | {};
+  disabled?: boolean;
 };
 
 const OpacityButton: FC<VariantProps> = ({
   onPress,
   children,
   containerStyles = {},
-  textStyles = {}
+  textStyles = {},
+  disabled = false
 }) => (
   <TouchableOpacity
     style={[styles.button, styles.opacityButton, containerStyles]}
     onPress={onPress}
+    disabled={disabled}
   >
     <Text style={[styles.text, styles.opacityText, textStyles]}>{children}</Text>
   </TouchableOpacity>

+ 2 - 1
src/components/Calendars/RangeCalendar/Navigation/index.tsx

@@ -4,11 +4,12 @@ import { View } from 'react-native';
 import { styles } from './style';
 import LeftArrow from '../../../../../assets/icons/left-arrow.svg';
 import RightArrow from '../../../../../assets/icons/right-arrow.svg';
+import { Colors } from 'src/theme';
 
 const Navigation = React.memo(({ direction }: { direction: 'prev' | 'next' }) => {
   return (
     <View style={[styles.navigationBtn, direction === 'prev' ? styles.prevComponent : styles.nextComponent]}>
-      {direction === 'prev' ? <LeftArrow /> : <RightArrow />}
+      {direction === 'prev' ? <LeftArrow fill={Colors.DARK_BLUE} /> : <RightArrow fill={Colors.DARK_BLUE} />}
     </View>
   );
 });

+ 110 - 68
src/components/Calendars/RangeCalendar/index.tsx

@@ -1,59 +1,104 @@
-import React, { useMemo, useState } from 'react';
+import React, { useState } from 'react';
 import { View } from 'react-native';
-import CalendarPicker, { CustomDatesStylesFunc, CustomDayHeaderStylesFunc } from 'react-native-calendar-picker';
 import moment from 'moment';
 import { Modal } from '../../Modal';
-import Navigation from './Navigation';
 
 import { styles } from './style';
 import { Colors } from '../../../theme';
+import { Calendar } from 'react-native-calendars';
+import { Button } from 'src/components/Button';
+import { ButtonVariants } from 'src/types/components';
 
-export default function RangeCalendar(
-  { isModalVisible, closeModal }: { isModalVisible: boolean, closeModal: () => void }
-) {
+export default function RangeCalendar({
+  isModalVisible,
+  closeModal,
+  allowRangeSelection = true,
+  disableFutureDates = false,
+}: {
+  isModalVisible: boolean;
+  closeModal: (startDate?: Date | null, endDate?: Date | null) => void;
+  allowRangeSelection?: boolean;
+  disableFutureDates?: boolean;
+}) {
   const [selectedStartDate, setSelectedStartDate] = useState<Date | null>(null);
   const [selectedEndDate, setSelectedEndDate] = useState<Date | null>(null);
 
-  const handleOnDateChange = (date: Date, type: string) => {
-    if (type === 'END_DATE') {
-      setSelectedEndDate(date);
-    } else {
-      setSelectedStartDate(date);
-      setSelectedEndDate(null);
-    }
+  const customThemeStyles = {
+    textSectionTitleColor: Colors.ORANGE,
+    todayTextColor: Colors.DARK_BLUE,
+    dayTextColor: Colors.DARK_BLUE,
+    textDisabledColor: Colors.LIGHT_GRAY,
+    dotColor: Colors.DARK_BLUE,
+    arrowColor: Colors.DARK_BLUE,
+    monthTextColor: Colors.DARK_BLUE,
+    indicatorColor: Colors.DARK_BLUE,
+    textDayFontWeight: 'normal',
+    textMonthFontWeight: 'bold',
+    textDayHeaderFontWeight: 'bold',
+    textDayFontSize: 14,
+    textMonthFontSize: 14,
+    textDayHeaderFontSize: 12,
+    selectedDayBackgroundColor: Colors.ORANGE,
+    'stylesheet.calendar.header': {
+      header: styles.calendarHeader
+    },
   };
 
-  const customDayHeaderStyles: CustomDayHeaderStylesFunc = () => {
-    return {
-      textStyle: styles.dayHeader,
-    };
+  const onDayPress = (day: any) => {
+    if (!allowRangeSelection) {
+      setSelectedStartDate(day.dateString);
+      return;
+    } 
+    if (!selectedStartDate || (selectedStartDate && selectedEndDate)) {
+      setSelectedStartDate(day.dateString);
+      setSelectedEndDate(null);
+    } else if (!selectedEndDate) {
+      if (day.dateString < selectedStartDate) {
+        setSelectedEndDate(selectedStartDate);
+        setSelectedStartDate(day.dateString);
+      } else {
+        setSelectedEndDate(day.dateString);
+      }
+    }
   };
 
-  const customSelectedDatesStyles: CustomDatesStylesFunc = useMemo(() => {
-    return (date: moment.Moment) => {
-      if (date.isSame(moment(), 'day')) {
-        return {
-          containerStyle: {},
-          textStyle: {
-            borderWidth: 1,
-            borderColor: Colors.DARK_BLUE,
-            borderRadius: 17,
-            height: 34,
-            width: 34,
-            textAlign: 'center',
-            verticalAlign: 'middle',
-          },
-          style: {
-            backgroundColor: Colors.WHITE,
-          }
-        };
+  const markedDates = (() => {
+    const marked: { [key: string]: any } = {};
+    let start = selectedStartDate as unknown as string;
+    let end = selectedEndDate as unknown as string;
+    if (disableFutureDates) {
+      const today = moment().add(1, 'day');
+      const lastDay = moment().add(2, 'years');
+      while (today.isBefore(lastDay, 'day')) {
+        const dateString = today.format('YYYY-MM-DD');
+        if (!marked[dateString]) {
+          marked[dateString] = {  disableTouchEvent: true, disabled: true };
+        }
+        today.add(1, 'day');
+      }
+    }
+    if (start && end) {
+      marked[start] = { startingDay: true, color: Colors.ORANGE, textColor: 'white' };
+      let day = start;
+      while (day < end) {
+        day = moment(day).add(1, 'days').format('YYYY-MM-DD');
+        if (day === end) {
+          marked[day] = { endingDay: true, color: Colors.ORANGE, textColor: 'white' };
+        } else {
+          marked[day] = { color: 'transparent', textColor: Colors.DARK_BLUE };
+        }
       }
-      return {
-        containerStyle: {},
-        textStyle: {},
+    } else if (start) {
+      marked[start] = {
+        selected: true,
+        startingDay: true,
+        endingDay: true,
+        color: Colors.ORANGE,
+        textColor: 'white',
       };
-    };
-  }, []);
+    }
+    return marked;
+  })();
 
   const resetSelections = () => {
     closeModal();
@@ -61,43 +106,40 @@ export default function RangeCalendar(
     setSelectedEndDate(null);
   };
 
-  const prevNavigationComponent = useMemo(() => <Navigation direction="prev" />, []);
-  const nextNavigationComponent = useMemo(() => <Navigation direction="next" />, []);
+  const handleClose = () => {
+    closeModal(selectedStartDate, selectedEndDate);
+    setSelectedStartDate(null);
+    setSelectedEndDate(null);
+  };
 
   return (
     <Modal
-      visibleInPercent={'70%'}
+      visibleInPercent={'auto'}
       visible={isModalVisible}
       onRequestClose={resetSelections}
-      headerTitle='Select Date'
+      headerTitle={allowRangeSelection ? "Select Dates" : "Select Date" }
     >
       <View style={styles.modalContent}>
-        <CalendarPicker
-          scaleFactor={400}
-          allowRangeSelection
-          allowBackwardRangeSelect
-          onDateChange={handleOnDateChange as any}
-          selectedStartDate={selectedStartDate as Date}
-          selectedEndDate={selectedEndDate as Date}
-          scrollable
-          showDayStragglers
-          previousComponent={prevNavigationComponent}
-          nextComponent={nextNavigationComponent}
-          dayLabelsWrapper={styles.labelsWrapper}
-          selectedRangeStyle={styles.rangeStyle}
-          selectedRangeEndTextStyle={styles.rangeStartEndTextStyle}
-          selectedRangeStartTextStyle={styles.rangeStartEndTextStyle}
-          selectedRangeEndStyle={[styles.rangeStartEndStyle, styles.rangeEndStyle]}
-          selectedRangeStartStyle={[styles.rangeStartEndStyle, styles.rangeStartStyle]}
-          customDayHeaderStyles={customDayHeaderStyles}
-          customDatesStyles={customSelectedDatesStyles}
-          disabledDatesTextStyle={styles.disabledDates}
-          textStyle={styles.textStyle}
-          monthTitleStyle={styles.dateTitle}
-          yearTitleStyle={styles.dateTitle}
-          headerWrapperStyle={styles.headerWrapper}
+        <Calendar
+          onDayPress={onDayPress}
+          markingType={'period'}
+          markedDates={markedDates}
+          enableSwipeMonths={true}
+          firstDay={1}
+          theme={{
+            ...customThemeStyles,
+          }}
         />
       </View>
+      <View style={styles.modalFooter}>
+        <Button
+          children="Done"
+          onPress={handleClose}
+          disabled={!selectedStartDate}
+          variant={!selectedStartDate ? ButtonVariants.OPACITY : ButtonVariants.FILL}
+          containerStyles={{ borderWidth: 0 }}
+         />
+      </View>
     </Modal>
   );
 }

+ 9 - 55
src/components/Calendars/RangeCalendar/style.ts

@@ -4,63 +4,17 @@ import { Colors } from '../../../theme';
 export const styles = StyleSheet.create({
   modalContent: {
     backgroundColor: Colors.WHITE,
-    padding: 22,
-    justifyContent: 'center',
-    alignItems: 'center',
+    paddingVertical: 22,
   },
-  rangeStartEndTextStyle: {
-    color: Colors.WHITE,
+  calendarHeader: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    marginBottom: 16,
+    alignItems: 'center'
   },
-  rangeStartEndStyle: {
-    width: 40,
-  },
-  rangeEndStyle: {
-    alignSelf: 'flex-start',
-    justifyContent: 'flex-start',
-    borderTopRightRadius: 20,
-    borderBottomRightRadius: 20,
-  },
-  rangeStartStyle: {
-    alignSelf: 'flex-end',
+  modalFooter: {
     justifyContent: 'flex-end',
-    borderTopLeftRadius: 20,
-    borderBottomLeftRadius: 20,
-  },
-  rangeStyle: {
-    backgroundColor: Colors.DARK_LIGHT,
-    height: 34,
-  },
-  labelsWrapper: {
-    borderTopWidth: 0,
-    borderBottomWidth: 0,
-    marginBottom: 5,
-  },
-  disabledDates: {
-    width: "100%",
-    height: "100%",
-    backgroundColor: Colors.WHITE,
-    textAlign: "center",
-    textAlignVertical: "center",
-  },
-  dateTitle: {
-    color: Colors.DARK_BLUE,
-    fontSize: 16,
-    lineHeight: 24,
-    fontWeight: '700'
-  },
-  headerWrapper: {
-    marginBottom: 15
-  },
-  textStyle: {
-    color: Colors.DARK_BLUE,
-    fontSize: 14,
-    lineHeight: 20
-  },
-  dayHeader: {
-    color: Colors.ORANGE,
-    fontSize: 12,
-    lineHeight: 16,
-    letterSpacing: 1,
-    fontWeight: '700'
+    width: '100%',
+    marginBottom: 24,
   }
 });

+ 38 - 22
src/components/FlatList/index.tsx

@@ -1,44 +1,56 @@
-import React, { FC, useEffect, useState } from 'react';
+import React, { FC, useCallback, useEffect, useState } from 'react';
 import { FlatList as List, SafeAreaView, View } from 'react-native';
 import { Input } from '../Input';
 import { styles } from './styles';
 import { Item, ItemData } from './item';
 import { useGetRegionsWithFlagQuery } from '@api/regions';
+import { useFocusEffect } from '@react-navigation/native';
+import { Loading } from '../Loading';
 
 type Props = {
   itemObject: (object: any) => void;
+  initialData?: ItemData[] | string[];
+  date?: boolean;
 };
 
 //TODO: rework to generic types + custom props
 
-export const FlatList: FC<Props> = ({ itemObject }) => {
-  const [selectedObject, setSelectedObject] = useState<{ name: string; id: number }>();
+export const FlatList: FC<Props> = ({ itemObject, initialData, date }) => {
+  const [selectedObject, setSelectedObject] = useState<{ name: string; id: number } | string>();
   const [search, setSearch] = useState('');
-  const [filteredData, setFilteredData] = useState<ItemData[]>([]);
-  const [masterData, setMasterData] = useState<ItemData[]>([]);
+  const [filteredData, setFilteredData] = useState<ItemData[] | string[]>([]);
+  const [masterData, setMasterData] = useState<ItemData[] | string[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
 
   const { data } = useGetRegionsWithFlagQuery(true);
 
-  useEffect(() => {
-    if (data) {
-      setFilteredData(data.data);
-      setMasterData(data.data);
-    }
-  }, [data]);
+  useFocusEffect(
+    useCallback(() => {
+      const dataToUse = initialData || data?.data;
+      if (dataToUse) {
+        setFilteredData(dataToUse);
+        setMasterData(dataToUse);
+      }
+      setLoading(false);
+    }, [data, initialData])
+  );
 
-  const selectItem = (object: { name: string; id: number }) => {
+  if (loading) return <Loading />;
+
+  const selectItem = (object: { name: string; id: number } | string) => {
     itemObject(object);
     setSelectedObject(object);
   };
 
   const searchFilter = (text: string) => {
     if (text) {
-      const newData = masterData.filter((item) => {
+      const newData = masterData.filter((item: any) => {
         const itemData = item.name ? item.name.toLowerCase() : ''.toLowerCase();
+        const initialData = item.country ? item.country.toLowerCase() : ''.toLowerCase();
         const textData = text.toLowerCase();
-        return itemData.indexOf(textData) > -1;
+        return itemData.indexOf(textData) > -1 || initialData.indexOf(textData) > -1;
       });
-      setFilteredData(newData);
+      setFilteredData(newData as string[] | ItemData[]);
       setSearch(text);
     } else {
       setFilteredData(masterData);
@@ -47,35 +59,39 @@ export const FlatList: FC<Props> = ({ itemObject }) => {
   };
 
   const renderItem = ({ item }: { item: ItemData }) => {
-    const selected = item.id === selectedObject?.id;
-
+    const selected = date
+      ? item === selectedObject
+      : item?.id === (selectedObject as { name: string; id: number })?.id;
     const backgroundColor = selected ? '#FAFAFA' : 'white';
 
     return (
       <Item
         selected={selected}
         item={item}
-        onPress={() => selectItem(item)}
+        onPress={() => selectItem(item as string | { name: string; id: number })}
         backgroundColor={backgroundColor}
+        initial={initialData ? true : false}
+        date={date}
       />
     );
   };
 
   return (
     <SafeAreaView style={styles.container}>
-      <View style={{ marginTop: 10 }}>
+      {!date && (
         <Input
           inputMode={'search'}
           placeholder={'Search'}
           onChange={(text) => searchFilter(text)}
           value={search}
         />
-      </View>
+      )}
       <List
-        data={filteredData}
+        data={filteredData as ItemData[]}
         renderItem={renderItem}
-        keyExtractor={(item) => item.id.toString()}
+        keyExtractor={(item) => (date ? item.toString() : item.id.toString())}
         extraData={selectedObject}
+        showsVerticalScrollIndicator={false}
       />
     </SafeAreaView>
   );

+ 38 - 16
src/components/FlatList/item.tsx

@@ -6,34 +6,54 @@ import { styles } from './styles';
 import MarkSVG from '../../../assets/icons/mark.svg';
 import { API_HOST } from '../../constants';
 
-export const Item = ({ item, onPress, backgroundColor, selected }: ItemProps) => {
-  const name = item.name.split('–');
+export const Item = ({ item, onPress, backgroundColor, selected, initial, date }: ItemProps) => {
+  const name = initial && date ? item : initial ? item.country : item.name?.split('–') || '';
 
   return (
     <TouchableOpacity onPress={onPress} style={[styles.item, { backgroundColor }]}>
+      {item?.country === 'All Regions' && <View style={{ width: 20 }}></View>}
       <View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 10 }}>
-        <Image
-          width={48}
-          height={48}
-          style={{ borderRadius: 48 / 2 }}
-          source={{
-            uri: `${API_HOST}/img/flags_new/${item.flag}`
-          }}
-        />
-        <View>
-          <Text style={[styles.title, { color: Colors.DARK_BLUE }]}>{name[0]}</Text>
-          <Text style={[styles.text, { color: Colors.DARK_BLUE }]}>{name[1]}</Text>
+        {item.flag && (
+          <Image
+            width={48}
+            height={48}
+            style={{ borderRadius: 48 / 2, borderWidth: 1, borderColor: Colors.LIGHT_GRAY }}
+            source={{
+              uri: initial ? `${API_HOST}${item.flag}` : `${API_HOST}/img/flags_new/${item.flag}`
+            }}
+          />
+        )}
+        <View style={{ flexShrink: 1 }}>
+          <View style={{ flexDirection: 'row' }}>
+            <Text style={[styles.title, { color: Colors.DARK_BLUE, flexShrink: 1 }]}>
+              {initial ? (name as string) : (name as string[])[0]}
+            </Text>
+            {initial && !date && item.country !== 'All Regions' && (
+              <View style={styles.regionIndicator}>
+                <Text style={[styles.text, { color: Colors.WHITE, fontWeight: 'bold' }]}>
+                  {item.dare ? 'DARE' : 'NM'}
+                </Text>
+              </View>
+            )}
+          </View>
+          {item.name && (
+            <Text style={[styles.text, { color: Colors.DARK_BLUE }]}>
+              {initial ? item.name : (name as string[])[1]}
+            </Text>
+          )}
         </View>
       </View>
-      <View style={{ marginRight: 10 }}>{selected && <MarkSVG />}</View>
+      <View style={{ marginRight: 10, width: 20 }}>{selected && <MarkSVG />}</View>
     </TouchableOpacity>
   );
 };
 
 export type ItemData = {
   id: number;
-  name: string;
-  flag: string;
+  name?: string;
+  flag?: string;
+  country?: string;
+  dare?: boolean;
 };
 
 type ItemProps = {
@@ -41,4 +61,6 @@ type ItemProps = {
   onPress: () => void;
   backgroundColor: string;
   selected: boolean;
+  initial?: boolean;
+  date?: boolean;
 };

+ 13 - 1
src/components/FlatList/styles.ts

@@ -1,10 +1,12 @@
 import { StatusBar, StyleSheet } from 'react-native';
 import { getFontSize } from '../../utils';
+import { Colors } from 'src/theme';
 
 export const styles = StyleSheet.create({
   container: {
     flex: 1,
-    gap: 15
+    gap: 15,
+    paddingVertical: 10
   },
   item: {
     width: '100%',
@@ -20,5 +22,15 @@ export const styles = StyleSheet.create({
   },
   text: {
     fontSize: getFontSize(12)
+  },
+  regionIndicator: {
+    height: 18,
+    overflow: 'hidden',
+    backgroundColor: Colors.DARK_BLUE,
+    borderRadius: 9,
+    paddingHorizontal: 6,
+    alignItems: 'center',
+    justifyContent: 'center',
+    marginLeft: 4
   }
 });

+ 4 - 2
src/components/Input/index.tsx

@@ -23,6 +23,7 @@ type Props = {
   icon?: ReactNode;
   multiline?: boolean;
   editable?: boolean;
+  height?: number;
 };
 
 export const Input: FC<Props> = ({
@@ -37,7 +38,8 @@ export const Input: FC<Props> = ({
   formikError,
   icon,
   multiline,
-  editable
+  editable,
+  height
 }) => {
   const [focused, setFocused] = useState(false);
 
@@ -50,7 +52,7 @@ export const Input: FC<Props> = ({
         style={[
           [styles.wrapper, formikError ? styles.inputError : null],
           { flexDirection: 'row', alignItems: 'center' },
-          multiline ? { height: 100 } : { height: 44 }
+          multiline ? { height: height ?? 100 } : { height: height ?? 44 }
         ]}
       >
         {icon ? (

+ 13 - 5
src/components/Modal/index.tsx

@@ -1,5 +1,11 @@
 import React, { FC, ReactNode } from 'react';
-import { Dimensions, DimensionValue, PixelRatio, View } from 'react-native';
+import {
+  Dimensions,
+  DimensionValue,
+  Platform,
+  StatusBar,
+  View
+} from 'react-native';
 import ReactModal from 'react-native-modal';
 import { ModalHeader } from './ModalHeader/modal-header';
 import { styles } from './style';
@@ -12,8 +18,6 @@ type Props = {
   headerTitle: string;
 };
 
-// TODO: heightPercentageToDP for all devices
-
 export const Modal: FC<Props> = ({
   children,
   onRequestClose,
@@ -21,7 +25,11 @@ export const Modal: FC<Props> = ({
   visibleInPercent,
   headerTitle
 }) => {
-  const screenHeight = Dimensions.get('window').height;
+  const screenHeight = Dimensions.get('screen').height;
+  const NOTCH_HEIGHT = 44;
+  const statusBarHeight =
+    Platform.OS === 'ios' ? StatusBar.currentHeight || NOTCH_HEIGHT : StatusBar.currentHeight || 0;
+  const adjustedHeight = screenHeight - statusBarHeight;
 
   return (
     <ReactModal
@@ -32,7 +40,7 @@ export const Modal: FC<Props> = ({
       statusBarTranslucent={true}
       presentationStyle="overFullScreen"
     >
-      <View style={[styles.wrapper, { height: visibleInPercent ?? screenHeight }]}>
+      <View style={[styles.wrapper, { height: visibleInPercent ?? adjustedHeight }]}>
         <ModalHeader onRequestClose={onRequestClose} textHeader={headerTitle} />
         <Drawer />
         {children}

+ 2 - 2
src/components/Modal/style.ts

@@ -16,6 +16,6 @@ export const styles = StyleSheet.create({
   },
   modal: {
     justifyContent: 'flex-end',
-    margin: 0,
-  },
+    margin: 0
+  }
 });

+ 65 - 7
src/components/WarningModal/index.tsx

@@ -14,18 +14,32 @@ import { Button } from '../Button';
 export const WarningModal = ({
   isVisible,
   onClose,
-  type
+  type,
+  message,
+  title,
+  action
 }: {
   isVisible: boolean;
   onClose: () => void;
   type: string;
+  message?: string;
+  title?: string;
+  action?: () => void;
 }) => {
   const navigation = useNavigation();
 
   const content = {
     offline: {
       message: 'Please check your Internet connection and try again.',
-      buttons: [{ text: 'OK', textColor: Colors.WHITE, color: Colors.DARK_BLUE, action: onClose }]
+      buttons: [
+        {
+          text: 'OK',
+          textColor: Colors.WHITE,
+          color: Colors.DARK_BLUE,
+          action: onClose,
+          borderColor: Colors.DARK_BLUE
+        }
+      ]
     },
     unauthorized: {
       message: 'To use this feature you need to have an account with NomadMania.',
@@ -37,7 +51,8 @@ export const WarningModal = ({
           action: () => {
             onClose();
             navigation.navigate(NAVIGATION_PAGES.LOGIN as never);
-          }
+          },
+          borderColor: Colors.DARK_BLUE
         },
         {
           text: 'Register',
@@ -46,9 +61,46 @@ export const WarningModal = ({
           action: () => {
             onClose();
             navigation.navigate(NAVIGATION_PAGES.REGISTER as never);
-          }
+          },
+          borderColor: Colors.DARK_BLUE
+        }
+      ]
+    },
+    delete: {
+      message,
+      buttons: [
+        {
+          text: 'No',
+          textColor: Colors.DARK_BLUE,
+          color: Colors.WHITE,
+          action: () => {
+            onClose();
+          },
+          borderColor: Colors.DARK_BLUE
+        },
+        {
+          text: 'Delete',
+          textColor: Colors.WHITE,
+          color: Colors.RED,
+          action: () => {
+            onClose();
+            action && action();
+          },
+          borderColor: Colors.RED
         }
       ]
+    },
+    success: {
+      message,
+      buttons: [
+        {
+          text: 'OK',
+          textColor: Colors.WHITE,
+          color: Colors.ORANGE,
+          action: onClose,
+          borderColor: Colors.ORANGE
+        },
+      ]
     }
   };
 
@@ -64,19 +116,25 @@ export const WarningModal = ({
             </TouchableOpacity>
           </View>
           <View style={styles.modalContent}>
-            <Text style={styles.modalTitle}>Oops!</Text>
+            <Text style={styles.modalTitle}>{title ?? 'Oops!'}</Text>
             <Text style={styles.modalText}>{modalContent.message}</Text>
             <View style={styles.buttonContainer}>
               {modalContent.buttons.map(
                 (
-                  button: { text: string; textColor: string; color: string; action: () => void },
+                  button: {
+                    text: string;
+                    textColor: string;
+                    color: string;
+                    action: () => void;
+                    borderColor: string;
+                  },
                   idx: number
                 ) => (
                   <Button
                     key={idx}
                     variant={ButtonVariants.OPACITY}
                     containerStyles={{
-                      borderColor: Colors.DARK_BLUE,
+                      borderColor: button.borderColor,
                       backgroundColor: button.color,
                       width: type === 'offline' ? '60%' : '45%'
                     }}

+ 2 - 1
src/components/WarningModal/styles.tsx

@@ -18,7 +18,8 @@ export const styles = StyleSheet.create({
     },
     shadowOpacity: 0.05,
     shadowRadius: 15,
-    elevation: 5
+    elevation: 5,
+    backgroundColor: Colors.WHITE
   },
   modalContent: {
     display: 'flex',

+ 1 - 1
src/constants/secrets.ts

@@ -1,5 +1,5 @@
 import Constants from 'expo-constants';
 
-export const { API_HOST } = Constants.manifest2?.extra?.expoClient?.extra ?? {};
+export const { API_HOST, MAP_HOST } = Constants.manifest2?.extra?.expoClient?.extra ?? {};
 
 export const API_URL = `${API_HOST}/webapi`;

+ 57 - 15
src/db/index.ts

@@ -4,43 +4,85 @@ import { Asset } from 'expo-asset';
 
 let db1: SQLite.SQLiteDatabase | null = null;
 let db2: SQLite.SQLiteDatabase | null = null;
+const nmRegionsDBname = 'nmRegions.db';
+const darePlacesDBname = 'darePlaces.db';
+const sqliteDirectory = 'SQLite';
+const sqliteFullPath = FileSystem.documentDirectory + sqliteDirectory;
+const DS = '/';
 
 async function copyDatabaseFile(dbName: string, dbAsset: Asset) {
+  console.log("DB copy start - " + dbName);
   await dbAsset.downloadAsync();
   await FileSystem.downloadAsync(
     dbAsset.uri,
-    FileSystem.documentDirectory + "SQLite/" + dbName
+    sqliteFullPath + DS + dbName
   );
-  
-  const dbUri = FileSystem.documentDirectory + `SQLite/${dbName}`;
+
+  const dbUri = sqliteFullPath + DS + dbName;
 
   await FileSystem.copyAsync({
     from: dbAsset.localUri ?? '',
     to: dbUri,
   });
-
   return dbUri;
 }
 
 export async function openDatabases() {
   try {
-    const sqlDir = FileSystem.documentDirectory + "SQLite";
-    const fileInfo = await FileSystem.getInfoAsync(sqlDir);
-
+    const fileInfo = await FileSystem.getInfoAsync(sqliteFullPath);
     if (!fileInfo.exists) {
-      await FileSystem.makeDirectoryAsync(sqlDir, { intermediates: true });
-      console.log('openDatabase - Downloading databases');
-      await copyDatabaseFile('nmRegions.db', Asset.fromModule(require('../../assets/db/nmRegions.db')));
-      await copyDatabaseFile('darePlaces.db', Asset.fromModule(require('../../assets/db/darePlaces.db')));
+      await FileSystem.makeDirectoryAsync(sqliteFullPath, { intermediates: true });
+    }
+
+    const nmRegionsDB = await FileSystem.getInfoAsync(sqliteFullPath + DS + nmRegionsDBname, { size: true });
+    if (!nmRegionsDB.exists) {
+      await copyDatabaseFile(nmRegionsDBname, Asset.fromModule(require('../../assets/db/' + nmRegionsDBname)));
+    }
+    if (nmRegionsDB.size == 0) {
+      await FileSystem.deleteAsync(sqliteFullPath + DS + nmRegionsDBname);
+      await copyDatabaseFile(nmRegionsDBname, Asset.fromModule(require('../../assets/db/' + nmRegionsDBname)));
+    }
+
 
-      console.log('openDatabase - Databases downloaded');
+    const darePlacesDB = await FileSystem.getInfoAsync(sqliteFullPath + DS + nmRegionsDBname, { size: true });
+    if (!darePlacesDB.exists) {
+      await copyDatabaseFile(darePlacesDBname, Asset.fromModule(require('../../assets/db/' + darePlacesDBname)));
     }
+    
+    if (darePlacesDB.size == 0) {
+      await FileSystem.deleteAsync(sqliteFullPath + DS + darePlacesDBname);
+      await copyDatabaseFile(darePlacesDBname, Asset.fromModule(require('../../assets/db/' + darePlacesDBname)));
+    }
+
+    const openDatabase = (dbName: string) => SQLite.openDatabase(dbName);
+    db1 = openDatabase(nmRegionsDBname);
+    db2 = openDatabase(darePlacesDBname);
+  } catch (error) {
+    console.error('openDatabases - Error:');
+    console.error(JSON.stringify(error, null, 2));
+  }
+}
+
+export async function refreshDatabases() {
+  try {
+    const fileInfo = await FileSystem.getInfoAsync(sqliteFullPath);
+    if (!fileInfo.exists) {
+      await FileSystem.makeDirectoryAsync(sqliteFullPath, { intermediates: true });
+    }
+    await FileSystem.deleteAsync(sqliteFullPath + DS + nmRegionsDBname, {idempotent: true});
+    await copyDatabaseFile(nmRegionsDBname, Asset.fromModule(require('../../assets/db/' + nmRegionsDBname)));
+
+    await FileSystem.deleteAsync(sqliteFullPath + DS +  darePlacesDBname , {idempotent: true} );
+    await copyDatabaseFile(darePlacesDBname, Asset.fromModule(require('../../assets/db/' + darePlacesDBname)));
 
     const openDatabase = (dbName: string) => SQLite.openDatabase(dbName);
-    db1 = openDatabase("nmRegions.db");
-    db2 = openDatabase("darePlaces.db");
+    db1 = null;
+    db2 = null;
+    db1 = openDatabase(nmRegionsDBname);
+    db2 = openDatabase(darePlacesDBname);
   } catch (error) {
-    console.error('openDatabase - Error:', error);
+    console.error('refreshDatabases - Error:');
+    console.error(JSON.stringify(error, null, 2));
   }
 }
 

+ 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) => ['getSlow', { token }] as const,
+  setSlow: () => ['setSlow'] 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;
+    }
+  });
+};

+ 38 - 0
src/modules/api/myDARE/dare-api.tsx

@@ -0,0 +1,38 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetMegaReturn extends ResponseType {
+  data: {
+    id: number;
+    name: string;
+    new: 0 | 1;
+  }[];
+}
+
+export interface PostGetRegionsDAREReturn extends ResponseType {
+  data: {
+    id: number;
+    name: string;
+    essential: 0 | 1;
+    flag1: string;
+    flag2: string | null;
+    visited: string;
+    new: 0 | 1;
+  }[];
+}
+
+export interface PostSetDARERegion {
+  token: string;
+  region: number;
+  visits: 0 | 1;
+}
+
+export const dareApi = {
+  getMegaregionsDare: (token: string) =>
+    request.postForm<PostGetMegaReturn>(API.GET_MEGAREGIONS_DARE, { token }),
+  getRegionsDare: (megaregion: number, token: string) =>
+    request.postForm<PostGetRegionsDAREReturn>(API.GET_REGIONS_DARE, { megaregion, token }),
+  setDARERegion: (data: PostSetDARERegion) =>
+    request.postForm<ResponseType>(API.SET_DARE_REGION, data)
+};

+ 6 - 0
src/modules/api/myDARE/dare-query-keys.tsx

@@ -0,0 +1,6 @@
+export const dareQueryKeys = {
+  getMegaregionsDare: (token: string) => ['getMegaregionsDare', { token }] as const,
+  getRegionsDare: (megaregion: number, token: string) =>
+    ['getRegionsDare', { megaregion, token }] as const,
+  setDARERegion: () => ['setDARERegion'] as const
+};

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

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

+ 3 - 0
src/modules/api/myDARE/queries/index.ts

@@ -0,0 +1,3 @@
+export * from './use-post-get-megaregions-dare';
+export * from './use-post-get-regions-dare';
+export * from './use-post-set-update-dare';

+ 17 - 0
src/modules/api/myDARE/queries/use-post-get-megaregions-dare.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { dareQueryKeys } from '../dare-query-keys';
+import { dareApi, type PostGetMegaReturn } from '../dare-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetMegaregionsDareQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetMegaReturn, BaseAxiosError>({
+    queryKey: dareQueryKeys.getMegaregionsDare(token),
+    queryFn: async () => {
+      const response = await dareApi.getMegaregionsDare(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/myDARE/queries/use-post-get-regions-dare.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { dareQueryKeys } from '../dare-query-keys';
+import { dareApi, type PostGetRegionsDAREReturn } from '../dare-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetRegionDareQuery = (megaregion: number, token: string, enabled: boolean) => {
+  return useQuery<PostGetRegionsDAREReturn, BaseAxiosError>({
+    queryKey: dareQueryKeys.getRegionsDare(megaregion, token),
+    queryFn: async () => {
+      const response = await dareApi.getRegionsDare(megaregion, token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/myDARE/queries/use-post-set-update-dare.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { dareQueryKeys } from '../dare-query-keys';
+import { type PostSetDARERegion, dareApi } from '../dare-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostSetDareRegionMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostSetDARERegion, ResponseType>({
+    mutationKey: dareQueryKeys.setDARERegion(),
+    mutationFn: async (data) => {
+      const response = await dareApi.setDARERegion(data);
+      return response.data;
+    }
+  });
+};

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

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

+ 4 - 0
src/modules/api/myRegions/queries/index.ts

@@ -0,0 +1,4 @@
+export * from './use-post-get-megaregions';
+export * from './use-post-get-regions-qe';
+export * from './use-post-set-update-nm';
+export * from './use-post-set-update-tcc';

+ 17 - 0
src/modules/api/myRegions/queries/use-post-get-megaregions.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { regionsApi, type PostGetMegaReturn } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetMegaregionsQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetMegaReturn, BaseAxiosError>({
+    queryKey: regionsQueryKeys.getMegaregions(token),
+    queryFn: async () => {
+      const response = await regionsApi.getMegaregions(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/myRegions/queries/use-post-get-regions-qe.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { regionsApi, type PostGetRegionsQeReturn } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetRegionQeQuery = (megaregion: number, token: string, enabled: boolean) => {
+  return useQuery<PostGetRegionsQeReturn, BaseAxiosError>({
+    queryKey: regionsQueryKeys.getRegionsQe(megaregion, token),
+    queryFn: async () => {
+      const response = await regionsApi.getRegionsQe(megaregion, token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/myRegions/queries/use-post-set-update-nm.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { type PostSetNmRegion, regionsApi } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostSetNmRegionMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostSetNmRegion, ResponseType>({
+    mutationKey: regionsQueryKeys.setNmRegion(),
+    mutationFn: async (data) => {
+      const response = await regionsApi.setNmRegion(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/myRegions/queries/use-post-set-update-tcc.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { type PostSetTCCRegion, regionsApi } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostSetTCCRegionMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostSetTCCRegion, ResponseType>({
+    mutationKey: regionsQueryKeys.setTCCRegion(),
+    mutationFn: async (data) => {
+      const response = await regionsApi.setTCCRegion(data);
+      return response.data;
+    }
+  });
+};

+ 64 - 0
src/modules/api/myRegions/regions-api.tsx

@@ -0,0 +1,64 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetMegaReturn extends ResponseType {
+  data: {
+    id: number;
+    name: string;
+  }[];
+}
+
+export interface PostGetRegionsQeReturn extends ResponseType {
+  data: {
+    out_regs: {
+      id: number;
+      flag_1: string;
+      flag_2: string;
+      region_name: string;
+      essential: 0 | 1;
+      quality: number;
+      year: number;
+      last: number;
+      visits: number;
+    }[];
+    out_tcc: {
+      id: number;
+      flag: string;
+      flag2: string;
+      name: string;
+      visited: number;
+    }[];
+    out_mtp: {
+      mtp_id: number;
+      flag: string;
+      flag2: string;
+      name: string;
+      visited: number;
+    }[];
+  };
+}
+
+export interface PostSetNmRegion {
+  token: string;
+  region: number;
+  first: number;
+  last: number;
+  visits: number;
+  quality: number;
+}
+
+export interface PostSetTCCRegion {
+  token: string;
+  region: number;
+  visits: 0 | 1;
+}
+
+export const regionsApi = {
+  getMegaregions: (token: string) =>
+    request.postForm<PostGetMegaReturn>(API.GET_MEGAREGIONS, { token }),
+  getRegionsQe: (megaregion: number, token: string) =>
+    request.postForm<PostGetRegionsQeReturn>(API.GET_REGIONS_QE, { megaregion, token }),
+  setNmRegion: (data: PostSetNmRegion) => request.postForm<ResponseType>(API.SET_NM_REGION, data),
+  setTCCRegion: (data: PostSetTCCRegion) => request.postForm<ResponseType>(API.SET_TCC_REGION, data)
+};

+ 6 - 0
src/modules/api/myRegions/regions-query-keys.tsx

@@ -0,0 +1,6 @@
+export const regionsQueryKeys = {
+  getMegaregions: (token: string) => ['getMegaregions', { token }] as const,
+  getRegionsQe: (megaregion: number, token: string) => ['getRegionsQe', { megaregion, token }] as const,
+  setNmRegion: () => ['setNmRegion'] as const,
+  setTCCRegion: () => ['setTCCRegion'] as const,
+};

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

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

+ 112 - 0
src/modules/api/photos/photos-api.tsx

@@ -0,0 +1,112 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetPhotosReturn extends ResponseType {
+  all_regions: {
+    id: number;
+    dare: boolean;
+    nm: boolean;
+    country: string;
+    name: string;
+    flag: string;
+  }[];
+  by_date: {
+    [key: string]: PhotosData[];
+  };
+  by_region: {
+    id: number;
+    country: string;
+    name: string | null;
+    flag: string;
+    nm: boolean;
+    dare: boolean;
+    photos: PhotosData[];
+  }[];
+  by_album: {
+    album_id: number;
+    album_name: string;
+    album_cover_photo: string;
+    photos: PhotosData[];
+  }[];
+}
+
+interface PhotosData {
+  id: number;
+  region_nm: number;
+  region_dare: number;
+  region: number;
+  title: string;
+  date: string;
+  url_small: string;
+  url_mid: string;
+  url_full: string;
+  album_id: number;
+  album_name: string;
+}
+
+export interface PostSetTempData {
+  token: string;
+  file: {
+    type: string;
+    uri: string;
+    name: string;
+  };
+}
+
+export interface PostSetSaveTemp {
+  token: string;
+  guids: string[];
+  date: string | null;
+  region: number;
+  description: string | null;
+}
+
+export interface PostGetTempReturn extends ResponseType {
+  photos: {
+    guid: string;
+    link: string;
+  }[];
+}
+
+export interface PostSetTempReturn extends ResponseType {
+  guid: string;
+  link: string;
+}
+
+export interface PostSetSaveTempReturn extends ResponseType {
+  saved: string[];
+  errors: string[];
+}
+
+export interface PostSetUpdatePhoto {
+  token: string;
+  photo_id: number;
+  date: string | null;
+  region: number | null;
+  description: string | null;
+}
+
+export const photosApi = {
+  getPhotosForUser: (token: string) =>
+    request.postForm<PostGetPhotosReturn>(API.GET_PHOTOS_FOR_USER, { token }),
+  setUploadTemp: (data: PostSetTempData) => {
+    const formData = new FormData();
+
+    formData.append('token', JSON.stringify(data.token));
+    formData.append('file', {
+      ...data.file
+    } as unknown as Blob);
+
+    return request.postForm<PostSetTempReturn>(API.UPLOAD_TEMP, formData);
+  },
+  getTemp: (token: string) => request.postForm<PostGetTempReturn>(API.GET_TEMP, { token }),
+  removeTemp: (token: string, guid: string) =>
+    request.postForm<ResponseType>(API.REMOVE_TEMP, { token, guid }),
+  saveTemp: (data: PostSetSaveTemp) => request.postForm<PostSetSaveTempReturn>(API.SAVE_TEMP, data),
+  deletePhoto: (token: string, photo_id: number) =>
+    request.postForm<ResponseType>(API.DELETE_PHOTO, { token, photo_id }),
+  updatePhoto: (data: PostSetUpdatePhoto) =>
+    request.postForm<PostSetSaveTempReturn>(API.UPDATE_PHOTO, data)
+};
+export { ResponseType };

+ 9 - 0
src/modules/api/photos/photos-query-keys.tsx

@@ -0,0 +1,9 @@
+export const photosQueryKeys = {
+  getPhotosForUser: (token: string) => ['getPhotosForUser', { token }] as const,
+  setUploadTemp: () => ['setUploadTemp'] as const,
+  getTemp: (token: string) => ['getTemp', { token }],
+  removeTemp: () => ['removeTemp'] as const,
+  saveTemp: () => ['saveTemp'] as const,
+  deletePhoto: () => ['deletePhoto'] as const,
+  updatePhoto: () => ['updatePhoto'] as const
+};

+ 7 - 0
src/modules/api/photos/queries/index.ts

@@ -0,0 +1,7 @@
+export * from './use-post-get-photos-for-user';
+export * from './use-post-set-upload-temp';
+export * from './use-post-get-temp';
+export * from './use-post-get-remove-temp';
+export * from './use-post-set-save-temp';
+export * from './use-post-delete-photo';
+export * from './use-post-update-photo';

+ 22 - 0
src/modules/api/photos/queries/use-post-delete-photo.tsx

@@ -0,0 +1,22 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { photosQueryKeys } from '../photos-query-keys';
+import { photosApi } from '../photos-api';
+import { ResponseType } from '../../response-type';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostDeletePhotoMutation = () => {
+  return useMutation<
+    ResponseType,
+    BaseAxiosError,
+    { token: string; photo_id: number },
+    ResponseType
+  >({
+    mutationKey: photosQueryKeys.deletePhoto(),
+    mutationFn: async (variables) => {
+      const response = await photosApi.deletePhoto(variables.token, variables.photo_id);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/photos/queries/use-post-get-photos-for-user.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { photosQueryKeys } from '../photos-query-keys';
+import { photosApi, type PostGetPhotosReturn } from '../photos-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetPhotosForUserQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetPhotosReturn, BaseAxiosError>({
+    queryKey: photosQueryKeys.getPhotosForUser(token),
+    queryFn: async () => {
+      const response = await photosApi.getPhotosForUser(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 22 - 0
src/modules/api/photos/queries/use-post-get-remove-temp.tsx

@@ -0,0 +1,22 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { photosQueryKeys } from '../photos-query-keys';
+import { photosApi } from '../photos-api';
+import { ResponseType } from '../../response-type';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostRemoveTempMutation = () => {
+  return useMutation<
+    ResponseType,
+    BaseAxiosError,
+    { token: string; guid: string },
+    ResponseType
+  >({
+    mutationKey: photosQueryKeys.removeTemp(),
+    mutationFn: async (variables) => {
+      const response = await photosApi.removeTemp(variables.token, variables.guid);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/photos/queries/use-post-get-temp.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { photosQueryKeys } from '../photos-query-keys';
+import { photosApi, type PostGetTempReturn } from '../photos-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetTempQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetTempReturn, BaseAxiosError>({
+    queryKey: photosQueryKeys.getTemp(token),
+    queryFn: async () => {
+      const response = await photosApi.getTemp(token);
+      return response.data;
+    },
+    enabled,
+  });
+};

+ 21 - 0
src/modules/api/photos/queries/use-post-set-save-temp.tsx

@@ -0,0 +1,21 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { photosQueryKeys } from '../photos-query-keys';
+import { type PostSetSaveTempReturn, type PostSetSaveTemp, photosApi } from '../photos-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostSaveTempMutation = () => {
+  return useMutation<
+    PostSetSaveTempReturn,
+    BaseAxiosError,
+    PostSetSaveTemp,
+    PostSetSaveTempReturn
+  >({
+    mutationKey: photosQueryKeys.saveTemp(),
+    mutationFn: async (data) => {
+      const response = await photosApi.saveTemp(data);
+      return response.data;
+    }
+  });
+};

+ 28 - 0
src/modules/api/photos/queries/use-post-set-upload-temp.tsx

@@ -0,0 +1,28 @@
+import axios from 'axios';
+import { type PostSetTempData } from '../photos-api';
+
+import { API } from '../../../../types';
+import { API_URL } from 'src/constants';
+
+export const postSetUploadTemp = async (data: PostSetTempData) => {
+  const url = API_URL + '/' + API.UPLOAD_TEMP;
+  const formData = new FormData();
+  formData.append('token', data.token);
+  formData.append('file', data.file as unknown as Blob);
+
+  const config = {
+    onUploadProgress: (progressEvent: ProgressEvent) => {
+      if (progressEvent.lengthComputable) {
+        const progress = progressEvent.loaded / progressEvent.total;
+        console.log('Upload progress:', progress);
+      }
+    }
+  };
+
+  try {
+    const response = await axios.post(url, formData, config as any);
+    return response.data;
+  } catch (error) {
+    console.error('Error', error);
+  }
+};

+ 21 - 0
src/modules/api/photos/queries/use-post-update-photo.tsx

@@ -0,0 +1,21 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { photosQueryKeys } from '../photos-query-keys';
+import { type PostSetSaveTempReturn, type PostSetUpdatePhoto, photosApi } from '../photos-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostUpdatePhotoMutation = () => {
+  return useMutation<
+    PostSetSaveTempReturn,
+    BaseAxiosError,
+    PostSetUpdatePhoto,
+    PostSetSaveTempReturn
+  >({
+    mutationKey: photosQueryKeys.updatePhoto(),
+    mutationFn: async (data) => {
+      const response = await photosApi.updatePhoto(data);
+      return response.data;
+    }
+  });
+};

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

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

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

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

+ 17 - 0
src/modules/api/travels/queries/use-post-get-kye.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { travelsQueryKeys } from '../travels-query-keys';
+import { travelsApi, type PostGetKyeReturn } from '../travels-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetKyeQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetKyeReturn, BaseAxiosError>({
+    queryKey: travelsQueryKeys.getKye(token),
+    queryFn: async () => {
+      const response = await travelsApi.getKye(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 21 - 0
src/modules/api/travels/queries/use-post-set-kye.tsx

@@ -0,0 +1,21 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { travelsQueryKeys } from '../travels-query-keys';
+import { travelsApi, type PostSetKyeReturn } from '../travels-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostSetKye = () => {
+  return useMutation<
+    PostSetKyeReturn,
+    BaseAxiosError,
+    { token: string; qid: number; visited: 0 | 1 },
+    PostSetKyeReturn
+  >({
+    mutationKey: travelsQueryKeys.setKye(),
+    mutationFn: async (variables) => {
+      const response = await travelsApi.setKye(variables.token, variables.qid, variables.visited);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/travels/travels-api.tsx

@@ -0,0 +1,17 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetKyeReturn extends ResponseType {
+  max: number;
+  regions: { qid: number; name: string }[];
+  visited: number[];
+}
+
+export interface PostSetKyeReturn extends ResponseType {}
+
+export const travelsApi = {
+  getKye: (token: string) => request.postForm<PostGetKyeReturn>(API.GET_KYE, { token }),
+  setKye: (token: string, qid: number, visited: 0 | 1) =>
+    request.postForm<PostSetKyeReturn>(API.SET_KYE, { token, qid, visited })
+};

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

@@ -0,0 +1,4 @@
+export const travelsQueryKeys = {
+  getKye: (token: string) => ['getKye', { token }] as const,
+  setKye: () => ['setKye'] as const
+};

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

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

+ 7 - 0
src/modules/api/trips/queries/index.ts

@@ -0,0 +1,7 @@
+export * from './use-post-get-trips-years';
+export * from './use-post-get-trips-for-year';
+export * from './use-post-get-regions-for-trips';
+export * from './use-post-delete-trip';
+export * from './use-post-get-trip';
+export * from './use-post-set-new-trip';
+export * from './use-post-set-update-trip';

+ 22 - 0
src/modules/api/trips/queries/use-post-delete-trip.tsx

@@ -0,0 +1,22 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { tripsApi } from '../trips-api';
+import { ResponseType } from '../../response-type';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostDeleteTripMutation = () => {
+  return useMutation<
+    ResponseType,
+    BaseAxiosError,
+    { token: string; trip_id: number },
+    ResponseType
+  >({
+    mutationKey: tripsQueryKeys.deleteTrip(),
+    mutationFn: async (variables) => {
+      const response = await tripsApi.deleteTrip(variables.token, variables.trip_id);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/trips/queries/use-post-get-regions-for-trips.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { tripsApi, type PostGetRegionsForTripsReturn } from '../trips-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetRegionsForTripsQuery = (enabled: boolean) => {
+  return useQuery<PostGetRegionsForTripsReturn, BaseAxiosError>({
+    queryKey: tripsQueryKeys.getRegionsForTrips(),
+    queryFn: async () => {
+      const response = await tripsApi.getRegionsForTrips();
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/trips/queries/use-post-get-trip.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { tripsApi, type PostGetTripReturn } from '../trips-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetTripQuery = (token: string, trip_id: number, enabled: boolean) => {
+  return useQuery<PostGetTripReturn, BaseAxiosError>({
+    queryKey: tripsQueryKeys.getTrip(token, trip_id),
+    queryFn: async () => {
+      const response = await tripsApi.getTrip(token, trip_id);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/trips/queries/use-post-get-trips-for-year.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { tripsApi, type PostGetTripsForYearReturn } from '../trips-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetTripsForYearQuery = (token: string, year: string, enabled: boolean) => {
+  return useQuery<PostGetTripsForYearReturn, BaseAxiosError>({
+    queryKey: tripsQueryKeys.getTripsForYear(token, year),
+    queryFn: async () => {
+      const response = await tripsApi.getTripsForYear(token, year);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/trips/queries/use-post-get-trips-years.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { tripsApi, type PostGetTripsYearsReturn } from '../trips-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetTripsYearsQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetTripsYearsReturn, BaseAxiosError>({
+    queryKey: tripsQueryKeys.getTripsYears(token),
+    queryFn: async () => {
+      const response = await tripsApi.getTripsYears(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 16 - 0
src/modules/api/trips/queries/use-post-set-new-trip.tsx

@@ -0,0 +1,16 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { type PostSetNewTripReturn, type PostSetNewTrip, tripsApi } from '../trips-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostSetNewTripMutation = () => {
+  return useMutation<PostSetNewTripReturn, BaseAxiosError, PostSetNewTrip, PostSetNewTripReturn>({
+    mutationKey: tripsQueryKeys.setNewTrip(),
+    mutationFn: async (data) => {
+      const response = await tripsApi.setNewTrip(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/trips/queries/use-post-set-update-trip.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { type PostUpdateTrip, tripsApi } from '../trips-api';
+import { ResponseType } from '../../response-type';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const usePostUpdateTripMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostUpdateTrip, ResponseType>({
+    mutationKey: tripsQueryKeys.updateTrip(),
+    mutationFn: async (data) => {
+      const response = await tripsApi.updateTrip(data);
+      return response.data;
+    }
+  });
+};

+ 95 - 0
src/modules/api/trips/trips-api.tsx

@@ -0,0 +1,95 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetTripsYearsReturn extends ResponseType {
+  data: string[];
+}
+
+export interface PostGetTripsForYearReturn extends ResponseType {
+  trips: {
+    id: number;
+    date_from: string;
+    date_to: string;
+    description: string;
+    regions: {
+      region_name: string;
+      flag1: string;
+      flag2: string | null;
+      status: 0 | 1;
+      id: number;
+    }[];
+  }[];
+}
+
+export interface RegionData {
+  id: number;
+  quality: number;
+  status: 0 | 1;
+  hidden: boolean;
+}
+
+export interface PostSetNewTrip {
+  token: string;
+  date_from: string;
+  date_to: string;
+  description: string;
+  regions: RegionData[];
+}
+
+export interface PostSetNewTripReturn extends ResponseType {
+  trip_id: number;
+}
+
+export interface PostUpdateTrip {
+  token: string;
+  trip_id: number;
+  date_from: string;
+  date_to: string;
+  description: string;
+  regions: RegionData[];
+}
+
+export interface PostGetTripReturn extends ResponseType {
+  trip: {
+    id: number;
+    date_from: string;
+    date_to: string;
+    description: string;
+    regions: {
+      id: number;
+      region_name: string;
+      flag1: string;
+      flag2: string | null;
+      quality: number;
+      status: 0 | 1;
+      hidden: boolean;
+      can_be_hidden: boolean;
+    }[];
+  };
+}
+
+export interface PostGetRegionsForTripsReturn extends ResponseType {
+  regions: {
+    id: number;
+    region_name: string;
+    flag1: string;
+    flag2: string | null;
+    hidden: boolean;
+  }[];
+}
+
+export const tripsApi = {
+  getTripsYears: (token: string) =>
+    request.postForm<PostGetTripsYearsReturn>(API.GET_TRIPS_YEARS, { token }),
+  getTripsForYear: (token: string, year: string) =>
+    request.postForm<PostGetTripsForYearReturn>(API.GET_TRIPS_FOR_YEAR, { token, year }),
+  setNewTrip: (data: PostSetNewTrip) =>
+    request.postForm<PostSetNewTripReturn>(API.SET_NEW_TRIP, data),
+  updateTrip: (data: PostUpdateTrip) => request.postForm<ResponseType>(API.UPDATE_TRIP, data),
+  deleteTrip: (token: string, trip_id: number) =>
+    request.postForm<ResponseType>(API.DELETE_TRIP, { token, trip_id }),
+  getTrip: (token: string, trip_id: number) =>
+    request.postForm<PostGetTripReturn>(API.GET_TRIP, { token, trip_id }),
+  getRegionsForTrips: () => request.get<PostGetRegionsForTripsReturn>(API.GET_REGIONS_FOR_TRIPS)
+};

+ 9 - 0
src/modules/api/trips/trips-query-keys.tsx

@@ -0,0 +1,9 @@
+export const tripsQueryKeys = {
+  getTripsYears: (token: string) => ['getTripsYears', { token }] as const,
+  getTripsForYear: (token: string, year: string) => ['getTripsForYear', { token, year }] as const,
+  setNewTrip: () => ['setNewTrip'] as const,
+  updateTrip: () => ['updateTrip'] as const,
+  deleteTrip: () => ['deleteTrip'] as const,
+  getTrip: (token: string, trip_id: number) => ['getTrip', { token, trip_id }] as const,
+  getRegionsForTrips: () => ['getRegionsForTrips'] as const
+};

+ 3 - 3
src/screens/InAppScreens/MapScreen/index.tsx

@@ -16,7 +16,7 @@ import regions from '../../../../assets/geojson/nm2022.json';
 import dareRegions from '../../../../assets/geojson/mqp.json';
 
 import NetInfo from '@react-native-community/netinfo';
-import { getFirstDatabase, getSecondDatabase } from '../../../db';
+import { getFirstDatabase, getSecondDatabase, refreshDatabases } from '../../../db';
 import { LocationPopup, RegionPopup, WarningModal } from '../../../components';
 
 import { styles } from './style';
@@ -42,8 +42,7 @@ import {
   Region,
   Series
 } from '../../../types/map';
-
-const MAP_HOST = 'https://maps.nomadmania.eu';
+import { MAP_HOST } from 'src/constants';
 
 const tilesBaseURL = `${MAP_HOST}/tiles_osm`;
 const localTileDir = `${FileSystem.cacheDirectory}tiles/background`;
@@ -318,6 +317,7 @@ const MapScreen: React.FC<MapScreenProps> = ({ navigation }) => {
         })
         .catch((error) => {
           console.error('Error fetching data', error);
+          refreshDatabases();
         });
 
       const bounds = turf.bbox(foundRegion);

+ 3 - 3
src/screens/InAppScreens/TravellersScreen/StatisticsScreen/index.tsx

@@ -90,12 +90,12 @@ const StatisticsScreen = () => {
                         return (
                           <TouchableOpacity
                             onPress={() =>
-                              navigation.navigate(NAVIGATION_PAGES.STATISTICS_LIST_DATA, {
+                              navigation.navigate(...([NAVIGATION_PAGES.STATISTICS_LIST_DATA, {
                                 title: item.name,
                                 type: route.title,
                                 url1: item.url1,
                                 url2: item.url2
-                              })
+                              }] as never))
                             }
                           >
                             <Text
@@ -107,7 +107,7 @@ const StatisticsScreen = () => {
                                 fontWeight: '700'
                               }}
                             >
-                              {item.name}
+                              {item.name}
                             </Text>
                           </TouchableOpacity>
                         );

+ 359 - 0
src/screens/InAppScreens/TravelsScreen/AddNewTripScreen/index.tsx

@@ -0,0 +1,359 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
+import ReactModal from 'react-native-modal';
+import { useNavigation } from '@react-navigation/native';
+
+import { PageWrapper, Header, Input, WarningModal } from 'src/components';
+import RegionItem from '../Components/RegionItem';
+import RangeCalendar from 'src/components/Calendars/RangeCalendar';
+
+import { StoreType, storage } from 'src/storage';
+import { Colors } from 'src/theme';
+import { NAVIGATION_PAGES } from 'src/types';
+import { RegionAddData } from '../utils/types';
+import {
+  useGetTripQuery,
+  usePostDeleteTripMutation,
+  usePostUpdateTripMutation,
+  usePostSetNewTripMutation
+} from '@api/trips';
+import { qualityOptions } from '../utils/constants';
+import { styles } from './styles';
+
+import CalendarSvg from '../../../../../assets/icons/calendar.svg';
+
+const AddNewTripScreen = ({ route }: { route: any }) => {
+  const editTripId = route.params?.editTripId ?? null;
+  const token = storage.get('token', StoreType.STRING) as string;
+  const { data: editData } = useGetTripQuery(token, editTripId, Boolean(editTripId));
+  const navigation = useNavigation();
+  const [calendarVisible, setCalendarVisible] = useState(false);
+  const [selectedDates, setSelectedDates] = useState<string | null>(null);
+  const [description, setDescription] = useState<string>('');
+  const [regions, setRegions] = useState<RegionAddData[] | null>(null);
+  const [disabled, setDisabled] = useState(true);
+  const [qualitySelectorVisible, setQualitySelectorVisible] = useState(false);
+  const [selectedRegionId, setSelectedRegionId] = useState<number | null>(null);
+  const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
+
+  const { mutate: saveNewTrip } = usePostSetNewTripMutation();
+  const { mutate: updateTrip } = usePostUpdateTripMutation();
+  const { mutate: deleteTrip } = usePostDeleteTripMutation();
+
+  useEffect(() => {
+    if (route.params?.regionsToSave) {
+      setRegions((currentRegions) => {
+        const newRegionsIds = route.params.regionsToSave.map((region: RegionAddData) => region.id);
+        const existingRegions = currentRegions?.filter((region) =>
+          newRegionsIds.includes(region.id)
+        );
+
+        const updatedRegions = route.params.regionsToSave.map((newRegion: RegionAddData) => {
+          const existingRegion = existingRegions?.find((region) => region.id === newRegion.id);
+
+          return {
+            ...newRegion,
+            quality: existingRegion ? existingRegion.quality : 3,
+            status: existingRegion ? existingRegion.status : 0,
+            can_be_hidden: existingRegion ? existingRegion.can_be_hidden : newRegion.hidden,
+            hidden: existingRegion ? existingRegion.hidden : false
+          };
+        });
+
+        return updatedRegions;
+      });
+    }
+  }, [route.params?.regionsToSave]);
+
+  function extractNumberAndExtension(path: string | null) {
+    if (!path) return null;
+    const slashIndex = path.lastIndexOf('/');
+    return path.substring(slashIndex + 1);
+  }
+
+  useEffect(() => {
+    if (editData && editData.trip) {
+      setSelectedDates(editData.trip.date_from + ' - ' + editData.trip.date_to);
+      setDescription(editData.trip.description);
+      setRegions(
+        editData.trip.regions.map((region: any) => {
+          return {
+            ...region,
+            flag1: extractNumberAndExtension(region.flag1),
+            flag2: extractNumberAndExtension(region.flag2)
+          };
+        })
+      );
+    }
+  }, [editData]);
+
+  useEffect(() => {
+    if (regions?.length && selectedDates) {
+      setDisabled(false);
+    } else {
+      setDisabled(true);
+    }
+  }, [regions, selectedDates]);
+
+  const changeQualityForRegion = (regionId: number | null, newQuality: number) => {
+    regions &&
+      setRegions(
+        regions.map((region) => {
+          if (region.id === regionId) {
+            return { ...region, quality: newQuality };
+          }
+          return region;
+        })
+      );
+  };
+
+  const changeStatusForRegion = (regionId: number | null) => {
+    regions &&
+      setRegions(
+        regions.map((region) => {
+          if (region.id === regionId) {
+            return { ...region, status: region.status === 1 ? 0 : 1 };
+          }
+          return region;
+        })
+      );
+  };
+
+  const changeHiddenForRegion = (regionId: number | null) => {
+    regions &&
+      setRegions(
+        regions.map((region) => {
+          if (region.id === regionId) {
+            return { ...region, hidden: !region.hidden };
+          }
+          return region;
+        })
+      );
+  };
+
+  const handleDeleteRegion = (regionId: number) => {
+    regions && setRegions(regions.filter((region) => region.id !== regionId));
+  };
+
+  const handleDeleteTrip = () => {
+    deleteTrip(
+      {
+        token,
+        trip_id: editTripId
+      },
+      {
+        onSuccess: () => {
+          setIsWarningModalVisible(false);
+          navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { deleted: true }] as never));
+        }
+      }
+    );
+  };
+
+  const handleSaveNewTrip = () => {
+    if (regions && selectedDates) {
+      const isStartDateInFuture =
+        selectedDates.split(' - ')[0] > new Date().toISOString().split('T')[0];
+      const regionsData = regions.map((region) => {
+        return {
+          id: region.id,
+          quality: region.quality ?? 3,
+          status: isStartDateInFuture ? 0 : (Number(region.status) as 0 | 1),
+          hidden: region.hidden
+        };
+      });
+
+      saveNewTrip(
+        {
+          token,
+          date_from: selectedDates.split(' - ')[0],
+          date_to: selectedDates.split(' - ')[1],
+          description,
+          regions: regionsData
+        },
+        {
+          onSuccess: () => {
+            navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { saved: true }] as never));
+          }
+        }
+      );
+    }
+  };
+
+  const handleUpdateTrip = () => {
+    if (regions && selectedDates) {
+      const isStartDateInFuture =
+        selectedDates.split(' - ')[0] > new Date().toISOString().split('T')[0];
+      const regionsData = regions.map((region) => {
+        return {
+          id: region.id,
+          quality: region.quality ?? 3,
+          status: isStartDateInFuture ? 0 : (Number(region.status) as 0 | 1),
+          hidden: region.hidden
+        };
+      });
+
+      updateTrip(
+        {
+          token,
+          trip_id: editTripId,
+          date_from: selectedDates.split(' - ')[0],
+          date_to: selectedDates.split(' - ')[1],
+          description,
+          regions: regionsData
+        },
+        {
+          onSuccess: (res) => {
+            navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { updated: true }] as never));
+          }
+        }
+      );
+    }
+  };
+
+  return (
+    <PageWrapper style={{ flex: 1 }}>
+      <Header label={editTripId ? 'Edit Trip' : 'Add New Trip'} />
+      <ScrollView
+        contentContainerStyle={{ flexGrow: 1, gap: 16 }}
+        showsVerticalScrollIndicator={false}
+      >
+        <TouchableOpacity style={styles.regionSelector} onPress={() => setCalendarVisible(true)}>
+          <CalendarSvg />
+          <Text style={styles.regionText}>{selectedDates ?? 'Add dates'}</Text>
+        </TouchableOpacity>
+
+        <Input
+          placeholder="Add description and all interesting moments of your trip"
+          inputMode={'text'}
+          onChange={(text) => setDescription(text)}
+          value={description}
+          header="Description"
+          height={54}
+          multiline={true}
+        />
+
+        <View style={{ marginBottom: 8 }}>
+          <Text style={styles.regionsLabel}>Regions</Text>
+          <TouchableOpacity
+            style={styles.addRegionBtn}
+            onPress={() =>
+              navigation.navigate(
+                ...([
+                  NAVIGATION_PAGES.ADD_REGIONS,
+                  { regionsParams: regions, editId: editTripId }
+                ] as never)
+              )
+            }
+          >
+            <Text style={styles.addRegionBtntext}>Add Region</Text>
+          </TouchableOpacity>
+          {regions && regions.length ? (
+            <View style={styles.regionsContainer}>
+              {regions.map((region) => {
+                return (
+                  <RegionItem
+                    key={region.id}
+                    region={region}
+                    onDelete={() => handleDeleteRegion(region.id)}
+                    onToggleStatus={() => changeStatusForRegion(region.id)}
+                    onQualityChange={() => {
+                      setSelectedRegionId(region.id);
+                      setQualitySelectorVisible(true);
+                    }}
+                    onHiddenChange={() => changeHiddenForRegion(region.id)}
+                    startDate={selectedDates ? selectedDates.split(' - ')[0] : null}
+                  />
+                );
+              })}
+            </View>
+          ) : (
+            <Text style={styles.noRegiosText}>No regions at the moment</Text>
+          )}
+        </View>
+      </ScrollView>
+
+      <View style={styles.tabContainer}>
+        {editTripId ? (
+          <>
+            <TouchableOpacity
+              style={[styles.tabStyle, styles.deleteTab]}
+              onPress={() => setIsWarningModalVisible(true)}
+            >
+              <Text style={[styles.tabText, styles.deleteTabText]}>Delete Trip</Text>
+            </TouchableOpacity>
+            <TouchableOpacity
+              style={[
+                styles.tabStyle,
+                styles.addNewTab,
+                disabled && { backgroundColor: Colors.LIGHT_GRAY, borderColor: Colors.LIGHT_GRAY }
+              ]}
+              onPress={handleUpdateTrip}
+              disabled={disabled}
+            >
+              <Text style={[styles.tabText, styles.addNewTabText]}>Save Trip</Text>
+            </TouchableOpacity>
+          </>
+        ) : (
+          <TouchableOpacity
+            style={[
+              styles.tabStyle,
+              styles.addNewTab,
+              disabled && { backgroundColor: Colors.LIGHT_GRAY, borderColor: Colors.LIGHT_GRAY },
+              { paddingVertical: 12 }
+            ]}
+            onPress={handleSaveNewTrip}
+            disabled={disabled}
+          >
+            <Text style={[styles.tabText, styles.addNewTabText]}>Add New Trip</Text>
+          </TouchableOpacity>
+        )}
+      </View>
+
+      <RangeCalendar
+        isModalVisible={calendarVisible}
+        closeModal={(startDate?: Date | null, endDate?: Date | null) => {
+          startDate &&
+            setSelectedDates(
+              startDate.toString() + ' - ' + (endDate ? endDate?.toString() : startDate?.toString())
+            );
+          setCalendarVisible(false);
+        }}
+      />
+      <ReactModal
+        isVisible={qualitySelectorVisible}
+        onBackdropPress={() => setQualitySelectorVisible(false)}
+        style={styles.modal}
+        statusBarTranslucent={true}
+        presentationStyle="overFullScreen"
+      >
+        <View style={styles.wrapper}>
+          <View style={{ paddingBottom: 16 }}>
+            {qualityOptions.map((option) => (
+              <TouchableOpacity
+                key={option.id}
+                style={styles.btnOption}
+                onPress={() => {
+                  setQualitySelectorVisible(false);
+                  changeQualityForRegion(selectedRegionId, option.id);
+                }}
+              >
+                <Text style={styles.btnOptionText}>{option.name}</Text>
+              </TouchableOpacity>
+            ))}
+          </View>
+        </View>
+      </ReactModal>
+      <WarningModal
+        type={'delete'}
+        isVisible={isWarningModalVisible}
+        onClose={() => setIsWarningModalVisible(false)}
+        title="Delete Trip"
+        message="Are you sure you want to delete your trip?"
+        action={handleDeleteTrip}
+      />
+    </PageWrapper>
+  );
+};
+
+export default AddNewTripScreen;

+ 100 - 0
src/screens/InAppScreens/TravelsScreen/AddNewTripScreen/styles.tsx

@@ -0,0 +1,100 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+import { getFontSize } from 'src/utils';
+
+export const styles = StyleSheet.create({
+  tabContainer: { flexDirection: 'row', gap: 16, alignItems: 'center', marginVertical: 8 },
+  tabStyle: {
+    flex: 1,
+    borderRadius: 4,
+    alignItems: 'center',
+    justifyContent: 'center',
+    paddingHorizontal: 16,
+    paddingVertical: 8,
+    gap: 4,
+    borderWidth: 1
+  },
+  deleteTab: {
+    backgroundColor: 'transparent',
+    borderColor: Colors.RED
+  },
+  tabText: {
+    fontSize: getFontSize(14),
+    fontWeight: 'bold',
+    fontFamily: 'redhat-700'
+  },
+  deleteTabText: {
+    color: Colors.RED
+  },
+  addNewTab: {
+    backgroundColor: Colors.ORANGE,
+    borderColor: Colors.ORANGE
+  },
+  addNewTabText: { color: Colors.WHITE },
+  regionSelector: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 16,
+    borderRadius: 4,
+    height: 36,
+    backgroundColor: 'rgba(250, 250, 250, 1)',
+    justifyContent: 'flex-start',
+    gap: 10
+  },
+  regionText: {
+    fontSize: 15,
+    color: Colors.LIGHT_GRAY
+  },
+  regionsLabel: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontFamily: 'redhat-700',
+    marginBottom: 5
+  },
+  noRegiosText: {
+    fontSize: 14,
+    fontWeight: '500',
+    color: Colors.LIGHT_GRAY,
+    textAlign: 'center',
+    paddingVertical: 8
+  },
+  addRegionBtn: {
+    display: 'flex',
+    justifyContent: 'center',
+    alignItems: 'center',
+    borderRadius: 4,
+    gap: 10,
+    padding: 10,
+    borderColor: Colors.DARK_BLUE,
+    borderWidth: 1,
+    borderStyle: 'solid',
+    marginBottom: 16
+  },
+  addRegionBtntext: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontFamily: 'redhat-700'
+  },
+  regionsContainer: {
+    gap: 12
+  },
+  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: 'auto'
+  },
+  modal: {
+    justifyContent: 'flex-end',
+    margin: 0
+  }
+});

+ 311 - 0
src/screens/InAppScreens/TravelsScreen/AddPhotoScreen/index.tsx

@@ -0,0 +1,311 @@
+import React, { useEffect, useState } from 'react';
+import { View, FlatList, Image, Text, TouchableOpacity } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import * as Progress from 'react-native-progress';
+import * as ImagePicker from 'expo-image-picker';
+
+import { Header, Input, Modal, PageWrapper, FlatList as List } from 'src/components';
+import { API_HOST } from 'src/constants';
+import { CustomButton } from '../Components';
+import RangeCalendar from 'src/components/Calendars/RangeCalendar';
+import { postSetUploadTemp, usePostRemoveTempMutation, usePostSaveTempMutation } from '@api/photos';
+
+import { StoreType, storage } from 'src/storage';
+import { AllRegions, ImageStatus, TempData } from '../utils/types';
+import { itemWidth } from '../utils';
+import { styles } from './styles';
+import { Colors } from 'src/theme';
+
+import AddImgSvg from '../../../../../assets/icons/travels-screens/add-img.svg';
+import ChooseSvg from '../../../../../assets/icons/travels-screens/choose.svg';
+import SaveSvg from '../../../../../assets/icons/travels-screens/save.svg';
+import XCircleSvg from '../../../../../assets/icons/travels-screens/x-circle.svg';
+import ChevronIcon from '../../../../../assets/icons/travels-screens/chevron-bottom.svg';
+import CalendarSvg from '../../../../../assets/icons/calendar.svg';
+
+const AddPhotoScreen = ({ route }: { route: any }) => {
+  const navigation: any = useNavigation();
+  const { data, images, allRegions, tempData } = route.params;
+  const token = storage.get('token', StoreType.STRING) as string;
+  const [isModalVisible, setIsModalVisible] = useState(false);
+  const [selectedRegion, setSelectedRegion] = useState<AllRegions | null>(
+    data && data.country
+      ? ({
+          id: data.id,
+          country: data.country
+        } as AllRegions)
+      : null
+  );
+  const [description, setDescription] = useState('');
+  const [selectedDate, setSelectedDate] = useState<string | null>(
+    data && data.date ? data.date : new Date().toISOString().slice(0, 10)
+  );
+  const { mutate: removeTemp } = usePostRemoveTempMutation();
+  const { mutate: saveTemp, data: saveResponse } = usePostSaveTempMutation();
+  const [imagesStatus, setImagesStatus] = useState<ImageStatus[]>(
+    images?.map((image: ImagePicker.ImagePickerAsset) => ({
+      uri: image.uri,
+      assetId: image.assetId,
+      loaded: false,
+      progress: 0,
+      uploadResponse: null,
+      dateTime: image.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(':', '-') ?? null
+    }))
+  );
+  const [calendarVisible, setCalendarVisible] = useState(false);
+
+  useEffect(() => {
+    if (tempData) {
+      setImagesStatus(
+        tempData.map((temp: TempData) => {
+          return { assetId: temp.guid, loaded: true, uploadResponse: temp };
+        })
+      );
+      return;
+    } else {
+      images.forEach((image: ImagePicker.ImagePickerAsset) => uploadTempImage(image));
+    }
+
+    const date = images[images.length - 1]?.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(
+      ':',
+      '-'
+    );
+    date && setSelectedDate(date);
+  }, [images]);
+
+  const uploadTempImage = async (image: ImagePicker.ImagePickerAsset) => {
+    const uriParts = image.uri.split('.');
+    const fileType = uriParts[uriParts.length - 1];
+
+    const imgData = {
+      token,
+      file: {
+        uri: image.uri,
+        name: image.uri.split('/').pop()!,
+        type: `image/${fileType}`
+      }
+    };
+
+    image.assetId && simulateUploadProgress(image.assetId);
+
+    postSetUploadTemp(imgData)
+      .then((response) => {
+        if (response && response.result === 'OK') {
+          setImagesStatus((currentStatus) =>
+            currentStatus.map((item) => {
+              if (item.assetId === image.assetId) {
+                return {
+                  ...item,
+                  loaded: true,
+                  progress: 1,
+                  uploadResponse: { guid: response.guid, link: response.link }
+                };
+              }
+              return item;
+            })
+          );
+        }
+      })
+      .catch((error) => {
+        console.error('Error', error);
+      });
+  };
+
+  const simulateUploadProgress = (assetId: string) => {
+    let progress = 0;
+    const intervalId = setInterval(() => {
+      progress += 0.05 + Math.random() * 0.25;
+      if (progress >= 1) {
+        clearInterval(intervalId);
+        progress = 1;
+      }
+
+      setImagesStatus((currentStatus) =>
+        currentStatus.map((item) => {
+          if (item.assetId === assetId) {
+            return { ...item, progress };
+          }
+          return item;
+        })
+      );
+    }, 300);
+  };
+
+  const deleteTemp = (item: ImageStatus) => {
+    removeTemp(
+      { token, guid: item.uploadResponse?.guid as string },
+      {
+        onSuccess: (res) => {
+          if (res.result === 'OK') {
+            setImagesStatus(
+              imagesStatus.filter(
+                (img: ImageStatus) => img.uploadResponse?.guid !== item.uploadResponse?.guid
+              )
+            );
+          }
+        }
+      }
+    );
+  };
+
+  const handleSelectPhoto = async () => {
+    await ImagePicker.launchImageLibraryAsync({
+      mediaTypes: ImagePicker.MediaTypeOptions.Images,
+      quality: 1,
+      allowsMultipleSelection: true,
+      exif: true
+    }).then((result) => {
+      if (!result.canceled) {
+        addPhoto(result.assets);
+      }
+    });
+  };
+
+  const addPhoto = (newImages: ImagePicker.ImagePickerAsset[]) => {
+    const date = newImages[newImages.length - 1]?.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(
+      ':',
+      '-'
+    );
+    date && setSelectedDate(date);
+    setImagesStatus([
+      ...imagesStatus,
+      ...newImages.map((image: any) => ({
+        uri: image.uri,
+        assetId: image.assetId,
+        loaded: false,
+        progress: 0,
+        uploadResponse: null,
+        dateTime: image.exif?.DateTimeOriginal?.split(' ')[0].replaceAll(':', '-') ?? null
+      }))
+    ]);
+    newImages.forEach((image: ImagePicker.ImagePickerAsset) => uploadTempImage(image));
+  };
+
+  const saveTempData = () => {
+    const tempData = {
+      token,
+      guids: imagesStatus.map((img) => img.uploadResponse?.guid!),
+      date: selectedDate,
+      region: selectedRegion?.id!,
+      description
+    };
+    saveTemp(tempData, {
+      onSuccess: () => {
+        data ? navigation.pop(2) : navigation.goBack();
+      }
+    });
+  };
+
+  const renderItem = ({ item }: { item: ImageStatus }) => (
+    <View style={styles.itemContainer}>
+      <View style={{ position: 'relative' }}>
+        {!item.loaded ? (
+          <Progress.Bar
+            progress={item.progress}
+            width={itemWidth}
+            color={Colors.DARK_BLUE}
+            borderWidth={0}
+            borderRadius={5}
+            unfilledColor="rgba(0, 0, 0, 0.1)"
+          />
+        ) : (
+          <Image
+            source={{ uri: API_HOST + item.uploadResponse?.link }}
+            style={[styles.image, { width: itemWidth, height: itemWidth }]}
+          />
+        )}
+        {item.loaded && (
+          <TouchableOpacity style={styles.deleteTemp} onPress={() => deleteTemp(item)}>
+            <XCircleSvg />
+          </TouchableOpacity>
+        )}
+      </View>
+    </View>
+  );
+
+  const getHeaderLabel = () => {
+    if (data?.country) return data.country;
+    if (data?.date) return data.date;
+    return 'Add photo';
+  };
+
+  return (
+    <PageWrapper>
+      <Header label={getHeaderLabel()} />
+      <View style={{ alignItems: 'center', marginBottom: 8 }}>
+        {data?.name && <Text style={styles.title}>{data.name}</Text>}
+        <View style={styles.btnContainer}>
+          <CustomButton
+            title="Save"
+            onPress={saveTempData}
+            icon={<SaveSvg fill={Colors.DARK_BLUE} />}
+            disabled={!selectedRegion || imagesStatus.length === 0}
+          />
+          <CustomButton
+            title="Add photo"
+            icon={<AddImgSvg fill={Colors.DARK_BLUE} />}
+            onPress={handleSelectPhoto}
+          />
+        </View>
+      </View>
+      <View style={{ gap: 10, marginVertical: 8 }}>
+        <Input
+          placeholder="Photo description"
+          inputMode={'text'}
+          onChange={(text) => setDescription(text)}
+          value={description}
+        />
+        {!data?.country && (
+          <TouchableOpacity
+            style={[styles.regionSelector, { justifyContent: 'space-between' }]}
+            onPress={() => setIsModalVisible(true)}
+          >
+            <Text style={styles.regionText}>{selectedRegion?.country ?? 'Choose a region'}</Text>
+            <ChevronIcon />
+          </TouchableOpacity>
+        )}
+
+        {!data?.date && (
+          <TouchableOpacity style={styles.regionSelector} onPress={() => setCalendarVisible(true)}>
+            <CalendarSvg />
+            <Text style={styles.regionText}>{selectedDate ?? 'Add date'}</Text>
+          </TouchableOpacity>
+        )}
+      </View>
+
+      <FlatList
+        data={imagesStatus}
+        renderItem={renderItem}
+        keyExtractor={(item, index) => index.toString()}
+        numColumns={2}
+        columnWrapperStyle={styles.columnWrapper}
+        showsVerticalScrollIndicator={false}
+      />
+      <Modal
+        onRequestClose={() => setIsModalVisible(false)}
+        headerTitle={'Select Region'}
+        visible={isModalVisible}
+      >
+        <List
+          itemObject={(object) => {
+            setSelectedRegion(object);
+            setIsModalVisible(false);
+          }}
+          initialData={allRegions}
+        />
+      </Modal>
+
+      <RangeCalendar
+        isModalVisible={calendarVisible}
+        closeModal={(startDate?: Date | null, endDate?: Date | null) => {
+          startDate && setSelectedDate(startDate.toString());
+          setCalendarVisible(false);
+        }}
+        allowRangeSelection={false}
+        disableFutureDates={true}
+      />
+    </PageWrapper>
+  );
+};
+
+export default AddPhotoScreen;

+ 40 - 0
src/screens/InAppScreens/TravelsScreen/AddPhotoScreen/styles.tsx

@@ -0,0 +1,40 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  columnWrapper: {
+    justifyContent: 'space-between'
+  },
+  image: {
+    borderRadius: 8
+  },
+  title: { color: '#808080', fontSize: 12, fontWeight: '500', paddingBottom: 16 },
+  regionSelector: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 16,
+    borderRadius: 4,
+    height: 36,
+    backgroundColor: 'rgba(250, 250, 250, 1)',
+    justifyContent: 'flex-start',
+    gap: 10
+  },
+  regionText: {
+    fontSize: 15,
+    color: Colors.LIGHT_GRAY
+  },
+  itemContainer: { alignItems: 'center', marginVertical: 8 },
+  deleteTemp: {
+    position: 'absolute',
+    right: -5,
+    top: -5,
+    padding: 10
+  },
+  btnContainer: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    gap: 15,
+    width: '85%'
+  }
+});

+ 282 - 0
src/screens/InAppScreens/TravelsScreen/AddRegionsScreen/index.tsx

@@ -0,0 +1,282 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { SafeAreaView, View, Text, Platform, TouchableOpacity } from 'react-native';
+import MapView, { Geojson, UrlTile } from 'react-native-maps';
+import * as turf from '@turf/turf';
+import { Feature } from '@turf/turf';
+import { useNavigation } from '@react-navigation/native';
+
+import { Header, Modal, FlatList as List } from 'src/components';
+
+import { MAP_HOST } from 'src/constants';
+import { Colors } from 'src/theme';
+import { findRegionInDataset } from 'src/utils/mapHelpers';
+import { calculateMapRegion } from '../utils/calculateRegion';
+import { FeatureCollection } from 'src/types/map';
+import { NAVIGATION_PAGES } from 'src/types';
+import { RegionAddData } from '../utils/types';
+import { useGetRegionsForTripsQuery } from '@api/trips';
+import { styles } from './styles';
+
+import regionsGeojson from '../../../../../assets/geojson/nm2022.json';
+import SearchSvg from '../../../../../assets/icons/search.svg';
+import SaveSvg from '../../../../../assets/icons/travels-screens/save.svg';
+
+const AddRegionsScreen = ({ route }: { route: any }) => {
+  const { regionsParams }: { regionsParams: RegionAddData[] } = route.params;
+  const { data } = useGetRegionsForTripsQuery(true);
+  const navigation = useNavigation();
+
+  const [regions, setRegions] = useState<RegionAddData[] | null>(null);
+  const [isModalVisible, setIsModalVisible] = useState(false);
+  const [selectedRegions, setSelectedRegions] = useState<FeatureCollection[]>([]);
+  const [regionsToSave, setRegionsToSave] = useState<RegionAddData[]>([]);
+  const [regionData, setRegionData] = useState<RegionAddData | null>(null);
+  const [regionPopupVisible, setRegionPopupVisible] = useState(false);
+  const mapRef = useRef<MapView>(null);
+
+  useEffect(() => {
+    if (data && data.regions) {
+      setRegions(data.regions);
+    }
+  }, [data]);
+
+  useEffect(() => {
+    const addRegionsAsync = async () => {
+      if (regionsParams) {
+        const promises = regionsParams.map((param) => {
+          const foundRegion: Feature | undefined = (
+            regionsGeojson as FeatureCollection
+          ).features.find((region) => region.properties?.id === param.id);
+          if (foundRegion) {
+            setRegionsToSave((prevRegions) => [...prevRegions, param]);
+            return {
+              type: 'FeatureCollection',
+              features: [
+                {
+                  geometry: foundRegion.geometry,
+                  properties: foundRegion.properties,
+                  type: 'Feature'
+                }
+              ]
+            };
+          }
+          return null;
+        });
+
+        const results = await Promise.all(promises);
+        const validRegions = results.filter(Boolean);
+
+        setSelectedRegions(
+          (prevSelectedRegions) => [...prevSelectedRegions, ...validRegions] as FeatureCollection[]
+        );
+      }
+    };
+
+    addRegionsAsync();
+  }, [regionsParams]);
+
+  const addRegionFromSearch = async (searchRegion: RegionAddData) => {
+    const foundRegion = (regionsGeojson as FeatureCollection).features.find(
+      (region) => region.properties?.id === searchRegion.id
+    );
+
+    if (foundRegion) {
+      const regionIndex = selectedRegions.findIndex(
+        (region) => region.features[0].properties?.id === searchRegion.id
+      );
+      const regionFromApi = regions?.find((region) => region.id === searchRegion.id);
+
+      if (regionIndex < 0 && regionFromApi) {
+        const newRegion = {
+          type: 'FeatureCollection',
+          features: [
+            {
+              geometry: foundRegion.geometry,
+              properties: foundRegion.properties,
+              type: 'Feature'
+            }
+          ]
+        };
+
+        setSelectedRegions([...selectedRegions, newRegion as FeatureCollection]);
+        setRegionsToSave((prevRegions) => [...prevRegions, regionFromApi]);
+        setRegionPopupVisible(true);
+
+        const bounds = turf.bbox(foundRegion);
+        const region = calculateMapRegion(bounds);
+        mapRef.current?.animateToRegion(region, 1000);
+      }
+    }
+  };
+
+  const handleSavePress = () => {
+    navigation.navigate(
+      ...([
+        NAVIGATION_PAGES.ADD_TRIP,
+        { regionsToSave: regionsToSave, editTripId: route.params?.editId }
+      ] as never)
+    );
+  };
+
+  const handleSetRegionData = (regionId: number) => {
+    const foundRegion = regions?.find((region) => region.id === regionId);
+
+    if (foundRegion) {
+      setRegionData(foundRegion);
+      setRegionsToSave((prevRegions) => [...prevRegions, foundRegion]);
+    }
+  };
+
+  const handleMapPress = useCallback(
+    async (event: {
+      nativeEvent: { coordinate: { latitude: any; longitude: any }; action?: string };
+    }) => {
+      if (event.nativeEvent?.action === 'polygon-press') return;
+
+      const { latitude, longitude } = event.nativeEvent.coordinate;
+      const point = turf.point([longitude, latitude]);
+
+      let foundRegion = findRegionInDataset(regionsGeojson, point);
+
+      if (foundRegion) {
+        const id = foundRegion.properties?.id;
+
+        const newRegion = {
+          type: 'FeatureCollection',
+          features: [
+            {
+              geometry: foundRegion.geometry,
+              properties: foundRegion.properties,
+              type: 'Feature'
+            }
+          ]
+        };
+
+        const regionIndex = selectedRegions.findIndex(
+          (region) => region.features[0].properties?.id === id
+        );
+
+        if (regionIndex >= 0) {
+          const newSelectedRegions = [...selectedRegions];
+          newSelectedRegions.splice(regionIndex, 1);
+          setSelectedRegions(newSelectedRegions);
+          setRegionsToSave(regionsToSave.filter((region) => region.id !== id));
+          setRegionPopupVisible(false);
+          return;
+        } else {
+          setSelectedRegions([...selectedRegions, newRegion] as FeatureCollection[]);
+        }
+
+        handleSetRegionData(id);
+        setRegionPopupVisible(true);
+
+        const bounds = turf.bbox(foundRegion);
+        const region = calculateMapRegion(bounds);
+
+        mapRef.current?.animateToRegion(region, 1000);
+      }
+    },
+    [selectedRegions, regions]
+  );
+
+  function renderGeoJSON() {
+    if (!selectedRegions || !selectedRegions.length) return null;
+
+    return selectedRegions.map((region, index) => (
+      <Geojson
+        key={index}
+        geojson={region as any}
+        fillColor="rgba(237, 147, 52, 0.5)"
+        strokeColor={Colors.ORANGE}
+        strokeWidth={Platform.OS == 'android' ? 2 : 1}
+        zIndex={3}
+        tracksViewChanges={false}
+      />
+    ));
+  }
+
+  const renderedGeoJSON = useMemo(() => renderGeoJSON(), [selectedRegions]);
+
+  return (
+    <SafeAreaView style={{ height: '100%' }}>
+      <View style={styles.wrapper}>
+        <Header label={'Add Regions'} />
+        <View style={styles.searchContainer}>
+          <TouchableOpacity style={[styles.regionSelector]} onPress={() => setIsModalVisible(true)}>
+            <SearchSvg fill={Colors.LIGHT_GRAY} />
+            <Text style={styles.regionText}>Search</Text>
+          </TouchableOpacity>
+          <TouchableOpacity
+            style={[
+              styles.saveBtn,
+              selectedRegions.length ? styles.saveBtnActive : styles.saveBtnDisabled
+            ]}
+            onPress={handleSavePress}
+            disabled={!selectedRegions.length}
+          >
+            <SaveSvg fill={selectedRegions.length ? Colors.WHITE : Colors.LIGHT_GRAY} />
+          </TouchableOpacity>
+        </View>
+      </View>
+
+      <View style={styles.container}>
+        <MapView
+          ref={mapRef}
+          style={styles.map}
+          showsMyLocationButton={false}
+          showsCompass={false}
+          zoomControlEnabled={false}
+          mapType={Platform.OS == 'android' ? 'none' : 'standard'}
+          maxZoomLevel={15}
+          minZoomLevel={0}
+          initialRegion={{
+            latitude: 0,
+            longitude: 0,
+            latitudeDelta: 180,
+            longitudeDelta: 180
+          }}
+          onPress={handleMapPress}
+        >
+          <UrlTile
+            urlTemplate={`${MAP_HOST}/tiles_osm/{z}/{x}/{y}`}
+            maximumZ={15}
+            maximumNativeZ={13}
+            shouldReplaceMapContent
+            minimumZ={0}
+          />
+          <UrlTile
+            urlTemplate={`${MAP_HOST}/tiles_nm/grid/{z}/{x}/{y}`}
+            maximumZ={15}
+            maximumNativeZ={13}
+            shouldReplaceMapContent
+            minimumZ={0}
+            opacity={0.3}
+          />
+          {renderedGeoJSON}
+        </MapView>
+      </View>
+      {regionPopupVisible && regionData && (
+        <View style={styles.popupWrapper}>
+          <View style={styles.popupContainer}>
+            <Text style={styles.popupText}>{regionData.name ?? regionData.region_name}</Text>
+          </View>
+        </View>
+      )}
+      <Modal
+        onRequestClose={() => setIsModalVisible(false)}
+        headerTitle={'Select Regions'}
+        visible={isModalVisible}
+      >
+        <List
+          itemObject={(object) => {
+            setIsModalVisible(false);
+            setRegionData(object);
+            addRegionFromSearch(object);
+          }}
+        />
+      </Modal>
+    </SafeAreaView>
+  );
+};
+
+export default AddRegionsScreen;

+ 73 - 0
src/screens/InAppScreens/TravelsScreen/AddRegionsScreen/styles.tsx

@@ -0,0 +1,73 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+import { getFontSize } from 'src/utils';
+
+export const styles = StyleSheet.create({
+  container: {
+    flex: 1
+  },
+  wrapper: {
+    marginLeft: '5%',
+    marginRight: '5%',
+    alignItems: 'center'
+  },
+  map: {
+    ...StyleSheet.absoluteFillObject
+  },
+  searchContainer: {
+    gap: 16,
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginBottom: 8
+  },
+  regionSelector: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 16,
+    borderRadius: 4,
+    height: 36,
+    backgroundColor: 'rgba(250, 250, 250, 1)',
+    justifyContent: 'flex-start',
+    gap: 10,
+    flex: 1
+  },
+  regionText: {
+    fontSize: 15,
+    fontWeight: '500',
+    color: Colors.LIGHT_GRAY
+  },
+  saveBtn: {
+    borderRadius: 20,
+    paddingVertical: 10,
+    paddingHorizontal: 16,
+    borderWidth: 1
+  },
+  saveBtnActive: {
+    borderColor: Colors.ORANGE,
+    backgroundColor: Colors.ORANGE
+  },
+  saveBtnDisabled: {
+    borderColor: Colors.LIGHT_GRAY
+  },
+  popupWrapper: {
+    marginHorizontal: 24,
+    position: 'absolute',
+    bottom: 25,
+    left: 0,
+    right: 0
+  },
+  popupContainer: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    alignSelf: 'center',
+    paddingVertical: 8,
+    paddingHorizontal: 16,
+    backgroundColor: 'rgba(33, 37, 41, 0.8)',
+    borderRadius: 100
+  },
+  popupText: {
+    color: Colors.WHITE,
+    fontSize: getFontSize(12),
+    fontWeight: '600'
+  }
+});

+ 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;

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

@@ -0,0 +1,101 @@
+import { Dimensions, StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+import { getFontSize } from 'src/utils';
+
+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 },
+  durationContainer: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    gap: 8,
+    flexWrap: 'wrap',
+    flex: 1
+  },
+  durationItem: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 4
+  },
+  durationIconActive: {
+    backgroundColor: Colors.WHITE
+  },
+  durationIconInactive: {
+    backgroundColor: Colors.LIGHT_GRAY
+  },
+  visitDuration: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    fontSize: getFontSize(12)
+  },
+  btnContainer: {
+    alignItems: 'center',
+    justifyContent: 'flex-end',
+    flexDirection: 'row',
+    gap: 8,
+    flex: 1
+  },
+  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,
+    flex: Dimensions.get('window').width < 380 ? 1 : 0
+  },
+  markVisitedButtonText: {
+    color: 'white',
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  visitedButtonText: {
+    color: Colors.DARK_BLUE,
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  visitedContainer: { gap: 6, flexDirection: 'row', alignItems: 'center' },
+  editScoreBtn: {
+    borderRadius: 20,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY,
+    alignItems: 'center',
+    justifyContent: 'center',
+    padding: 7
+  }
+});

+ 36 - 0
src/screens/InAppScreens/TravelsScreen/Components/CustomButton.tsx

@@ -0,0 +1,36 @@
+import { Text, TouchableOpacity, View } from 'react-native';
+import { styles } from './styles';
+import { Colors } from 'src/theme';
+
+export const CustomButton = ({
+  onPress,
+  title,
+  icon,
+  isActive,
+  disabled,
+  flex = 1
+}: {
+  onPress: () => void;
+  title?: string;
+  icon?: JSX.Element;
+  isActive?: boolean;
+  disabled?: boolean;
+  flex?: number;
+}) => (
+  <TouchableOpacity
+    style={[
+      styles.buttonStyle,
+      isActive ? styles.buttonActive : styles.buttonInactive,
+      { flex: icon && title ? 0.7 : flex, opacity: disabled ? 0.5 : 1 }
+    ]}
+    onPress={onPress}
+    disabled={disabled}
+  >
+    {icon && (
+      <View style={title ? { marginRight: 5 } : { paddingHorizontal: 16, paddingVertical: 8 }}>
+        {icon}
+      </View>
+    )}
+    {title && <Text style={[styles.buttonText, isActive && { color: Colors.WHITE }]}>{title}</Text>}
+  </TouchableOpacity>
+);

+ 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' }
+});

Some files were not shown because too many files changed in this diff