From 121eb320d3d935116b11427620a33c41d3bd20d0 Mon Sep 17 00:00:00 2001 From: Mayank Kumawat Date: Fri, 5 Jan 2024 18:16:09 +0530 Subject: [PATCH] changes of env toggle --- app/components/Environment/Environment.tsx | 101 ++++ app/components/MainScreen/BottomBar.tsx | 3 +- app/components/MainScreen/NavDrawer/index.tsx | 49 +- app/components/Navigator/MainNavigator.tsx | 2 + app/components/Navigator/index.tsx | 11 +- app/components/index.tsx | 2 + app/environment.tsx | 26 + .../en/user/editUserProfilelabels.json | 3 +- app/reducers/provider.tsx | 26 +- app/redux/slices/envSlice.tsx | 29 ++ app/redux/store.ts | 13 + app/repositories/default.ts | 2 + app/repositories/migrations/schema20.ts | 461 ++++++++++++++++++ app/utils/api.ts | 11 +- app/utils/axiosInstance.js | 16 + app/utils/index.tsx | 16 +- package-lock.json | 130 +++++ package.json | 2 + 18 files changed, 865 insertions(+), 38 deletions(-) create mode 100644 app/components/Environment/Environment.tsx create mode 100644 app/environment.tsx create mode 100644 app/redux/slices/envSlice.tsx create mode 100644 app/redux/store.ts create mode 100644 app/repositories/migrations/schema20.ts create mode 100644 app/utils/axiosInstance.js diff --git a/app/components/Environment/Environment.tsx b/app/components/Environment/Environment.tsx new file mode 100644 index 000000000..fb9840fa0 --- /dev/null +++ b/app/components/Environment/Environment.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import FontAwesome5Icon from 'react-native-vector-icons/FontAwesome5'; + +import { View, Text, StyleSheet, TouchableOpacity, Dimensions } from 'react-native'; + +import { ENV_TYPE } from '../../environment'; +import HeaderV2 from '../Common/Header/HeaderV2'; +import { Colors, Typography } from '../../styles'; +import { IS_ANDROID, scaleSize } from '../../styles/mixins'; +import { SET_APP_ENVIRONMENT } from '../../redux/slices/envSlice'; + +const { width, height } = Dimensions.get('screen'); + +const Environment = () => { + const { currentEnv } = useSelector(state => state.envSlice); + const dispatch = useDispatch(); + const insects = useSafeAreaInsets(); + + return ( + + + + dispatch(SET_APP_ENVIRONMENT(ENV_TYPE.STAGING))}> + + + + + Development + + + dispatch(SET_APP_ENVIRONMENT(ENV_TYPE.PROD))}> + + + + + Production + + + + + ); +}; + +export default Environment; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + btnCon: { + flexDirection: 'row', + justifyContent: 'space-evenly', + alignItems: 'center', + }, + btn: { + borderRadius: 8, + borderWidth: 2, + width: width / 2.5, + height: width / 2.5, + marginVertical: height / 30, + borderColor: Colors.PRIMARY, + justifyContent: 'center', + alignItems: 'center', + }, + iconCon: { + justifyContent: 'center', + alignItems: 'center', + backgroundColor: Colors.PRIMARY + '40', + borderRadius: 100, + width: 70, + height: 70, + }, + drawerItemLabel: { + fontSize: Typography.FONT_SIZE_16, + fontFamily: Typography.FONT_FAMILY_BOLD, + marginTop: scaleSize(12), + color: Colors.PRIMARY, + }, +}); diff --git a/app/components/MainScreen/BottomBar.tsx b/app/components/MainScreen/BottomBar.tsx index 97be5cb7b..a9d18dd46 100644 --- a/app/components/MainScreen/BottomBar.tsx +++ b/app/components/MainScreen/BottomBar.tsx @@ -1,4 +1,4 @@ -import { Text, View, StyleSheet, TouchableOpacity } from 'react-native'; +import { Text, View, StyleSheet, TouchableOpacity, Vibration } from 'react-native'; import React, { useRef, useState } from 'react'; import { SvgXml } from 'react-native-svg'; import { add_icon, ListIcon, PlotIcon, MapExplore } from '../../assets'; @@ -30,6 +30,7 @@ const BottomBar = ({ state, descriptors, navigation }: IBottomBarProps) => { const _addOptionsRef = useRef(); const onAddPress = () => { + Vibration.vibrate(); setOpen(prev => !prev); }; diff --git a/app/components/MainScreen/NavDrawer/index.tsx b/app/components/MainScreen/NavDrawer/index.tsx index e69c363c5..ddce3b413 100644 --- a/app/components/MainScreen/NavDrawer/index.tsx +++ b/app/components/MainScreen/NavDrawer/index.tsx @@ -4,6 +4,7 @@ import React, { useContext } from 'react'; import { useNavigation } from '@react-navigation/native'; import Ionicons from 'react-native-vector-icons/Ionicons'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; +import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Skeleton } from 'moti/skeleton/react-native-linear-gradient'; import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native'; @@ -26,6 +27,7 @@ import { UserContext } from '../../../reducers/user'; import { Colors, Spacing, Typography } from '../../../styles'; import { InventoryContext } from '../../../reducers/inventory'; import { auth0Login, auth0Logout } from '../../../actions/user'; +import { isPlantForThePlanetEmail } from '../../../utils'; const { protocol, cdnUrl, webAppUrl } = APIConfig; @@ -43,6 +45,12 @@ const getIcon = screenName => { return ; case 'Logs': return ; + case 'Environment': + return ( + + + + ); default: return undefined; } @@ -62,6 +70,8 @@ const getLabel = screenName => { return i18next.t('label.manage_offline'); case 'Logs': return i18next.t('label.activity_logs'); + case 'Environment': + return i18next.t('label.environment'); default: return undefined; } @@ -83,19 +93,10 @@ const NavDrawer = props => { const isLogout = await auth0Logout(userDispatch); }; - const onPressImprint = () => { - openWebView(`https://pp.eco/legal/${i18next.language}/imprint`); - }; - const onPressPolicy = () => { - openWebView(`https://pp.eco/legal/${i18next.language}/privacy`); - }; - const onPressTerms = () => { - openWebView(`https://pp.eco/legal/${i18next.language}/terms`); - }; - - const onPressEdit = () => { - openWebView(`${protocol}://${webAppUrl}/login`); - }; + const onPressImprint = () => openWebView(`https://pp.eco/legal/${i18next.language}/imprint`); + const onPressPolicy = () => openWebView(`https://pp.eco/legal/${i18next.language}/privacy`); + const onPressTerms = () => openWebView(`https://pp.eco/legal/${i18next.language}/terms`); + const onPressEdit = () => openWebView(`${protocol}://${webAppUrl}/login`); const onPressLogin = async () => { setLoading(true); @@ -186,6 +187,20 @@ const NavDrawer = props => { ), )} + {isPlantForThePlanetEmail(userState?.email) && ( + + navigation.navigate('Environment')}> + + {getIcon('Environment')} + {getLabel('Environment')} + + + + + )} {userState?.accessToken && ( + ); } diff --git a/app/components/Navigator/index.tsx b/app/components/Navigator/index.tsx index 5efd8ac78..cb6765261 100644 --- a/app/components/Navigator/index.tsx +++ b/app/components/Navigator/index.tsx @@ -1,5 +1,6 @@ -import { StatusBar, Platform, View } from 'react-native'; +import { useDispatch } from 'react-redux'; import React, { useEffect, useContext } from 'react'; +import { StatusBar, Platform, View } from 'react-native'; import { useNetInfo } from '@react-native-community/netinfo'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; @@ -13,6 +14,7 @@ import { NavigationContext } from '../../reducers/navigation'; import InitialLoadingNavigator from './InitialLoadingNavigator'; import { checkLoginAndSync } from '../../utils/checkLoginAndSync'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { SET_APP_ENVIRONMENT } from '../../redux/slices/envSlice'; const Stack = createStackNavigator(); const isAndroid = Platform.OS === 'android'; @@ -22,6 +24,7 @@ export default function AppNavigator() { const { showInitialStack } = useContext(NavigationContext); const netInfo = useNetInfo(); const insets = useSafeAreaInsets(); + const reduxDispatch = useDispatch(); const { state: inventoryState, dispatch } = useContext(InventoryContext); const { state: userState, dispatch: userDispatch } = useContext(UserContext); @@ -57,6 +60,12 @@ export default function AppNavigator() { userState.accessToken, ]); + useEffect(() => { + if (userState?.appEnvironment) { + reduxDispatch(SET_APP_ENVIRONMENT(userState.appEnvironment)); + } + }, [userState?.appEnvironment]); + useEffect(() => { if (!showInitialStack) { autoSync(); diff --git a/app/components/index.tsx b/app/components/index.tsx index 1d7fa380c..82dc569bc 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -26,8 +26,10 @@ import ManageProjects from '../screens/ManageProjectsFlow/ManageProjects'; import SelectProject from './Projects/SelectProject'; import SpecieSampleTree from './SpecieSampleTree'; import CreateIntervention from './CreateIntervention/CreateIntervention'; +import Environment from './Environment/Environment'; export { + Environment, CreateIntervention, RegisterTree, TPOQuestion, diff --git a/app/environment.tsx b/app/environment.tsx new file mode 100644 index 000000000..87cbb28da --- /dev/null +++ b/app/environment.tsx @@ -0,0 +1,26 @@ +import Config from 'react-native-config'; + +export enum ENV_TYPE { + STAGING = 'STAGING', + PROD = 'PRODUCTION', +} + +export const ENVS = { + STAGING: { + BUGSNAP_CLIENT_KEY: Config.BUGSNAP_CLIENT_KEY, + AUTH0_DOMAIN: 'accounts.plant-for-the-planet.org', + AUTH0_CLIENT_ID: 'LiEqEef641Pzv8cBGn6i9Jt9jrnyLJEt', + API_ENDPOINT: 'app-staging.plant-for-the-planet.org', + CDN_URL: 'cdn.plant-for-the-planet.org/staging', + WEB_APP_URL: 'dev.pp.eco', + }, + PRODUCTION: { + BUGSNAP_CLIENT_KEY: 'e1b5d94f16186f8c6a2169882998ebda', + AUTH0_DOMAIN: 'accounts.plant-for-the-planet.org', + AUTH0_CLIENT_ID: 'LiEqEef641Pzv8cBGn6i9Jt9jrnyLJEt', + API_ENDPOINT: 'app.plant-for-the-planet.org', + CDN_URL: 'cdn.plant-for-the-planet.org', + WEB_APP_URL: 'www1.plant-for-the-planet.org', + REALM_DISABLE_ANALYTICS: true, + }, +}; diff --git a/app/languages/en/user/editUserProfilelabels.json b/app/languages/en/user/editUserProfilelabels.json index c88d0f16f..e847688ae 100644 --- a/app/languages/en/user/editUserProfilelabels.json +++ b/app/languages/en/user/editUserProfilelabels.json @@ -20,5 +20,6 @@ "manage_projects": "Manage Projects", "additional_data": "Additional Data", "project_config": "Project Configuration", - "login_signup": "Login / Signup" + "login_signup": "Login / Signup", + "environment": "Environment" } diff --git a/app/reducers/provider.tsx b/app/reducers/provider.tsx index 741067447..56076ca89 100644 --- a/app/reducers/provider.tsx +++ b/app/reducers/provider.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Provider as ReduxProvider } from 'react-redux'; import { InventoryContextProvider } from './inventory'; import { LoadingContextProvider } from './loader'; import { SpeciesContextProvider } from './species'; @@ -7,22 +8,25 @@ import { NavigationContextProvider } from './navigation'; import { AdditionalDataContextProvider } from './additionalData'; import { PlantLocationHistoryContextProvider } from './plantLocationHistory'; import { ProjectContextProvider } from './project'; +import { store } from '../redux/store'; export default function Provider({ children }) { return ( - - - - - - {children} - - - - - + + + + + + + {children} + + + + + + ); diff --git a/app/redux/slices/envSlice.tsx b/app/redux/slices/envSlice.tsx new file mode 100644 index 000000000..dc004d29d --- /dev/null +++ b/app/redux/slices/envSlice.tsx @@ -0,0 +1,29 @@ +import { createSlice } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; + +import { ENV_TYPE } from '../../environment'; +import { modifyUserDetails } from '../../repositories/user'; + +interface EnvState { + currentEnv: string; +} + +const initialState: EnvState = { + currentEnv: ENV_TYPE.STAGING, +}; + +export const envSlice = createSlice({ + name: 'envSlice', + initialState, + reducers: { + SET_APP_ENVIRONMENT: (state, action: PayloadAction) => { + modifyUserDetails({ + appEnvironment: action.payload, + }); + state.currentEnv = action.payload; + }, + }, +}); + +export const { SET_APP_ENVIRONMENT } = envSlice.actions; +export default envSlice.reducer; diff --git a/app/redux/store.ts b/app/redux/store.ts new file mode 100644 index 000000000..881f87089 --- /dev/null +++ b/app/redux/store.ts @@ -0,0 +1,13 @@ +import { configureStore } from '@reduxjs/toolkit'; +import envSlice from './slices/envSlice'; + +export const store = configureStore({ + reducer: { + envSlice, + }, +}); + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch; diff --git a/app/repositories/default.ts b/app/repositories/default.ts index 7a17888fc..d3b9f172e 100644 --- a/app/repositories/default.ts +++ b/app/repositories/default.ts @@ -20,6 +20,7 @@ import schema16 from './migrations/schema16'; import schema17 from './migrations/schema17'; import schema18 from './migrations/schema18'; import schema19 from './migrations/schema19'; +import schema20 from './migrations/schema20'; export const schemas = [ schema0, @@ -42,6 +43,7 @@ export const schemas = [ schema17, schema18, schema19, + schema20, ]; export const getSchema = () => schemas[schemas.length - 1]; diff --git a/app/repositories/migrations/schema20.ts b/app/repositories/migrations/schema20.ts new file mode 100644 index 000000000..7c007e381 --- /dev/null +++ b/app/repositories/migrations/schema20.ts @@ -0,0 +1,461 @@ +import { ENV_TYPE } from '../../environment'; +import { InventoryType } from '../../types/inventory'; + +// schema version +const schemaVersion = 20; + +// SCHEMAS +const Coordinates = { + name: 'Coordinates', + properties: { + latitude: 'double', + longitude: 'double', + imageUrl: 'string?', + cdnImageUrl: 'string?', + currentloclat: 'double', + currentloclong: 'double', + isImageUploaded: 'bool?', + coordinateID: 'string?', + }, +}; + +const Polygons = { + name: 'Polygons', + properties: { + isPolygonComplete: 'bool?', + coordinates: 'Coordinates[]', + }, +}; + +const Species = { + name: 'Species', + properties: { + aliases: 'string', + treeCount: 'int', + id: 'string?', + }, +}; + +const OfflineMaps = { + name: 'OfflineMaps', + primaryKey: 'name', + properties: { + areaName: 'string', + size: 'int', + name: 'string', + }, +}; + +const AdditionalDetail = { + name: 'AdditionalDetail', + properties: { + key: 'string', + value: 'string', + // refer [dataTypes] from [additionalDataConstants] + accessType: 'string', + }, +}; + +const remeasurementDates = { + name: 'RemeasurementDates', + properties: { + sampleTreeId: 'string', + created: 'date', + lastMeasurement: 'date?', + remeasureBy: 'date', + nextMeasurement: 'date?', + }, +}; + +const PlantLocationHistory = { + name: 'PlantLocationHistory', + primaryKey: 'id', + properties: { + // id of this history + id: 'string', + eventName: 'string?', + // date when this history was created + eventDate: 'date', + // URL of the image if picture was clicked + imageUrl: 'string?', + // CDN URL of the image if picture was clicked + cdnImageUrl: 'string?', + // diameter of selected species + diameter: 'double?', + // height of selected species + height: 'double?', + // stores the additional details for the registration + additionalDetails: 'AdditionalDetail[]', + // stores the app metadata. Needs to be stringified as it might contain nested array/objects + appMetadata: 'string?', + // status of the event or plant location history + status: 'string?', + // reason for the current status + statusReason: 'string?', + // status of data maintained for updating and uploading of the plant location history + dataStatus: 'string?', + // id of the plant location + parentId: 'string?', + // if the plant location history is for sample tree then add the sample tree index + samplePlantLocationIndex: 'int?', + //last navigation screen + lastScreen: 'string?', + }, +}; + +// used to record the sample trees +const SampleTrees = { + name: 'SampleTrees', + properties: { + // stores the latitude of the tree + latitude: 'double', + // stores the longitude of the tree + longitude: 'double', + // stores the latitude of the device when location was marked + deviceLatitude: 'double', + // stores the longitude of the device when location was marked + deviceLongitude: 'double', + // stores accuracy of location when the location was marked + locationAccuracy: 'double?', + // URL of the image if picture was clicked + imageUrl: 'string?', + // CDN URL of the image if picture was clicked + cdnImageUrl: 'string?', + // specie id for this sample tree + specieId: 'string?', + // specie name of specie id for this sample tree + specieName: 'string?', + // diameter of selected specie + specieDiameter: 'double?', + // height of selected specie + specieHeight: 'double?', + // tag id of the tree if the tree has one + tagId: 'string?', + // current status of the tree. Refer to inventoryConstants for different status + status: { type: 'string', default: 'INCOMPLETE' }, + // stores the date when the tree was planted + plantationDate: 'date?', + // stores the location id when the data upload is successful + locationId: 'string?', + // stores the tree type which is always sample tree + treeType: { type: 'string', default: 'sample' }, + // stores the additional details for the registration + additionalDetails: 'AdditionalDetail[]', + // stores the app metadata. Needs to be stringified as it might contain nested array/objects + appMetadata: 'string?', + // stores the hid when registration is uploaded successfully + hid: 'string?', + // stores the plant location version history + plantLocationHistory: 'PlantLocationHistory[]', + //store remeasurement related dates + remeasurementDates: 'RemeasurementDates?', + }, +}; + +const Inventory = { + name: 'Inventory', + primaryKey: 'inventory_id', + properties: { + inventory_id: 'string', + plantation_date: 'date?', + treeType: 'string?', + status: 'string?', + projectId: 'string?', + donationType: 'string?', + locateTree: 'string?', + lastScreen: 'string?', + species: 'Species[]', + polygons: 'Polygons[]', + specieDiameter: 'double?', + specieHeight: 'double?', // <*IMPORTANT*> ONLY FOR SINGLE TREE + tagId: 'string?', + registrationDate: 'date?', + // stores the count of sample trees which are to be recorded + sampleTreesCount: 'int?', + // stores the sample trees having length equal to tree count + sampleTrees: 'SampleTrees[]', + // stores the number of sample trees which are already recorded + completedSampleTreesCount: { + type: 'int?', + default: 0, + }, + // stores the number of sample trees which are uploaded to server + uploadedSampleTreesCount: { + type: 'int?', + default: 0, + }, + // stores the location id of the plant location which is available + // when the inventory data is uploaded + locationId: 'string?', + // stores the additional details for the registration + additionalDetails: 'AdditionalDetail[]', + // stores the app metadata. Needs to be stringified as it might contain nested array/objects + appMetadata: 'string?', + // stores the hid when registration is uploaded successfully + hid: 'string?', + // stores the original geoJSON of coordinates in string which was uploaded + // for the first time for a registration + originalGeometry: 'string?', + // stores the plant location version history + plantLocationHistory: 'PlantLocationHistory[]', + }, +}; + +const User = { + name: 'User', + primaryKey: 'id', + properties: { + id: 'string?', + accessToken: 'string?', + idToken: 'string?', + email: 'string?', + firstName: 'string?', + lastName: 'string?', + image: 'string?', + country: 'string?', + isLogEnabled: 'bool?', + userId: 'string?', + userType: 'string?', + refreshToken: 'string?', + isSignUpRequired: 'bool?', + type: 'string?', + displayName: 'string?', + // stores the expiry time of token in seconds + expirationTime: 'int?', + fetchNecessaryInventoryFlag: { type: 'int', default: InventoryType.NecessaryItems }, + fetchGivenMonthsInventoryFlag: 'int?', + appEnvironment: 'string', + }, +}; + +// used to store the logs of a feature or a flow +const ActivityLogs = { + name: 'ActivityLogs', + primaryKey: 'id', + properties: { + // id of the log + id: 'string', + // id of the feature or flow this log is linked to. (optional) + referenceId: 'string?', + // defines the type of log. Refer to constants - LogTypes + logType: 'string', + // defines the log level. Refer constants - LogLevels + logLevel: 'string', + // time at which the log was created or modified + timestamp: 'date', + // text which is to be logged + message: 'string', + // version of tree mapper app + appVersion: 'string', + // status code for api request (optional) + statusCode: 'string?', + // used to show extra details if available (optional) + logStack: 'string?', + }, +}; + +// used to store all the available scientific species extracted from zip +const ScientificSpecies = { + name: 'ScientificSpecies', + primaryKey: 'guid', + properties: { + // stores the guid of scientific specie + guid: 'string', + // stores the name of scientific specie and indexed for better search + scientificName: { type: 'string', indexed: true }, + // used to check if this specie is preferred by user or not. Default to [false] + isUserSpecies: { type: 'bool', default: false }, + // used to check whether this specie is synced to server or not. Defaults to [false] + // This property is used with [isUserSpecies] + isUploaded: { type: 'bool', default: false }, + // stores the specieId which is uploaded on server + specieId: 'string?', + aliases: { type: 'string', default: '' }, + image: { type: 'string', default: '' }, + description: { type: 'string', default: '' }, + isUpdated: { type: 'bool', default: true }, + }, +}; + +const ProjectSites = { + name: 'ProjectSites', + primaryKey: 'id', + properties: { + id: 'string', + name: 'string', + description: 'string?', + status: 'string?', + geometry: 'string', + }, +}; + +// used to store all the available scientific species extracted from zip +const Projects = { + name: 'Projects', + primaryKey: 'id', + properties: { + id: 'string', + slug: 'string', + allowDonations: 'bool', + countPlanted: 'int', + countTarget: 'int', + currency: 'string', + image: 'string', + country: 'string', + name: 'string', + treeCost: 'double?', + sites: 'ProjectSites[]', + // stores the geometry of the project + geometry: 'string', + frequency: { type: 'string', default: 'Default' }, //in number of days + intensity: { type: 'int', default: 100 }, //percentage of sample to be remeasured + }, +}; + +// dropdown options for dropdown field +const DropdownOption = { + name: 'DropdownOption', + embedded: true, + properties: { + key: 'string', + value: 'string', + }, +}; + +// Element Type - Dropdown +const Dropdown = { + name: 'Dropdown', + primaryKey: 'id', + properties: { + id: 'string', + parentId: { + type: 'string', + indexed: true, + }, + defaultValue: 'string?', + isRequired: { + type: 'bool', + default: false, + }, + dropdownOptions: 'DropdownOption[]', + }, +}; + +// Element Type - Input +const Input = { + name: 'Input', + primaryKey: 'id', + properties: { + id: 'string', + parentId: { + type: 'string', + indexed: true, + }, + defaultValue: 'string?', + isRequired: { + type: 'bool', + default: false, + }, + type: 'string', + regexValidation: 'string?', + }, +}; + +// Element Type - YesNo +const YesNo = { + name: 'YesNo', + primaryKey: 'id', + properties: { + id: 'string', + parentId: { + type: 'string', + indexed: true, + }, + defaultValue: { + type: 'bool', + default: false, + }, + isRequired: { + type: 'bool', + default: false, + }, + }, +}; + +// Stores details of a single field which then is stores in form fields list +const Element = { + name: 'Element', + primaryKey: 'id', + properties: { + id: 'string', + key: 'string', + name: 'string', + type: 'string', + treeType: 'string[]', + registrationType: 'string[]', + // refer [dataTypes] from [additionalDataConstants] + accessType: { type: 'string', default: 'private' }, + }, +}; + +// Stores all the forms(multi steps) created by the user +const Form = { + name: 'Form', + primaryKey: 'id', + properties: { + id: 'string', + order: 'int', + // stores list of all the elements for a form + elements: 'Element[]', + title: 'string?', + description: 'string?', + }, +}; + +// Stores all the metadata added by the user from Metadata UI +const Metadata = { + name: 'Metadata', + primaryKey: 'id', + properties: { + id: 'string', + key: 'string', + value: 'string', + order: 'int', + // refer [dataTypes] from [additionalDataConstants] + accessType: { type: 'string', default: 'private' }, + }, +}; + +// migration to delete all the SYNCED registrations +const migration = (oldRealm: any, newRealm: any) => { + // checkAndMarkMissingData({ oldRealm, newRealm, schemaVersion, isMigration: true }); + // deleteSyncedAndMigrate(oldRealm, newRealm, schemaVersion); +}; + +export default { + schema: [ + Coordinates, + Polygons, + User, + OfflineMaps, + Species, + Inventory, + ScientificSpecies, + ActivityLogs, + SampleTrees, + Projects, + DropdownOption, + Dropdown, + Input, + YesNo, + Element, + Form, + Metadata, + AdditionalDetail, + ProjectSites, + PlantLocationHistory, + remeasurementDates, + ], + schemaVersion, + migration, +}; diff --git a/app/utils/api.ts b/app/utils/api.ts index 5eb07fab6..4cb92dca5 100644 --- a/app/utils/api.ts +++ b/app/utils/api.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; import jwtDecode from 'jwt-decode'; import { Platform } from 'react-native'; import 'react-native-get-random-values'; @@ -8,20 +7,12 @@ import { bugsnag } from './index'; import { nanoid } from 'nanoid'; import { LogTypes } from './constants'; import dbLog from '../repositories/logs'; -import { APIConfig } from '../actions/Config'; +import axiosInstance from './axiosInstance'; import { getUserDetails } from '../repositories/user'; import { isInternetConnected } from './checkInternet'; import { name as packageName, version } from '../../package.json'; import { checkErrorCode, getNewAccessToken } from '../actions/user'; -const { protocol, url: baseURL } = APIConfig; - -// creates and axios instance with base url -const axiosInstance = axios.create({ - baseURL: `${protocol}://${baseURL}`, -}); -console.log(baseURL, 'baseURL'); - // Add a request interceptor which adds the configuration in all the requests axiosInstance.interceptors.request.use( async config => { diff --git a/app/utils/axiosInstance.js b/app/utils/axiosInstance.js new file mode 100644 index 000000000..3e670c1c5 --- /dev/null +++ b/app/utils/axiosInstance.js @@ -0,0 +1,16 @@ +// axiosInstance.js +import axios from 'axios'; +import { ENVS } from '../environment'; +import { store } from '../redux/store'; + +const axiosInstance = axios.create({ + baseURL: `${'https'}://${ENVS[store.getState().envSlice.currentEnv].API_ENDPOINT}`, +}); + +store.subscribe(() => { + axiosInstance.defaults.baseURL = `${'https'}://${ + ENVS[store.getState().envSlice.currentEnv].API_ENDPOINT + }`; +}); + +export default axiosInstance; diff --git a/app/utils/index.tsx b/app/utils/index.tsx index f2590be3e..5d850096d 100644 --- a/app/utils/index.tsx +++ b/app/utils/index.tsx @@ -12,4 +12,18 @@ const isWithinLastMonths = (registrationDate: string, monthsCount: number): bool return diffBool; }; -export { ALPHABETS, bugsnag, isWithinLastMonths }; +const isPlantForThePlanetEmail = (email: string) => { + const domain = 'plant-for-the-planet.org'; + // Convert email to lowercase for case-insensitive comparison + const lowerCaseEmail = email?.toLowerCase(); + + // Check if the email ends with the specified domain + const endsWithDomain = lowerCaseEmail?.endsWith(domain); + + // Check if the email contains "plant-for-the-planet" before the domain + const containsPlantForThePlanet = lowerCaseEmail?.includes('plant-for-the-planet'); + + return endsWithDomain && containsPlantForThePlanet; +}; + +export { ALPHABETS, bugsnag, isWithinLastMonths, isPlantForThePlanetEmail }; diff --git a/package-lock.json b/package-lock.json index 8985b0471..47510a640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@react-navigation/bottom-tabs": "^6.5.8", "@react-navigation/native": "^6.1.1", "@react-navigation/stack": "^6.3.10", + "@reduxjs/toolkit": "^2.0.1", "@turf/area": "^6.5.0", "@turf/bbox": "^6.5.0", "@turf/boolean-point-in-polygon": "^6.5.0", @@ -68,6 +69,7 @@ "react-native-tab-view": "^3.3.4", "react-native-vector-icons": "^10.0.0", "react-native-zip-archive": "^5.0.6", + "react-redux": "^9.0.4", "realm": "^11.10.2" }, "devDependencies": { @@ -5133,6 +5135,34 @@ "node-fetch": "^2.6.0" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.0.1.tgz", + "integrity": "sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.0", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/reselect": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.0.1.tgz", + "integrity": "sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==" + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -5991,6 +6021,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/yargs": { "version": "17.0.29", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", @@ -10205,6 +10240,15 @@ "node": ">=14.0.0" } }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -15878,6 +15922,32 @@ "async-limiter": "~1.0.0" } }, + "node_modules/react-redux": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz", + "integrity": "sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", @@ -16067,6 +16137,19 @@ "node": ">= 4" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -21819,6 +21902,24 @@ "node-fetch": "^2.6.0" } }, + "@reduxjs/toolkit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.0.1.tgz", + "integrity": "sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==", + "requires": { + "immer": "^10.0.3", + "redux": "^5.0.0", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "dependencies": { + "reselect": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.0.1.tgz", + "integrity": "sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==" + } + } + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -22461,6 +22562,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/yargs": { "version": "17.0.29", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", @@ -25559,6 +25665,11 @@ "queue": "6.0.2" } }, + "immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -29783,6 +29894,15 @@ "resolved": "https://registry.npmjs.org/react-native-zip-archive/-/react-native-zip-archive-5.0.6.tgz", "integrity": "sha512-AKc8qk2JlCwoijnwxzt0BRwCl6LOcMAGgd0+aF51LOZEeL+ePJzDBzZLkcIB1z2tnMtdpDUG3q/4O8Q49wgC0Q==" }, + "react-redux": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz", + "integrity": "sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==", + "requires": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + } + }, "react-refresh": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", @@ -29921,6 +30041,16 @@ "tslib": "^2.0.1" } }, + "redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==" + }, "reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", diff --git a/package.json b/package.json index cfaaa8020..c6d65a3ba 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@react-navigation/bottom-tabs": "^6.5.8", "@react-navigation/native": "^6.1.1", "@react-navigation/stack": "^6.3.10", + "@reduxjs/toolkit": "^2.0.1", "@turf/area": "^6.5.0", "@turf/bbox": "^6.5.0", "@turf/boolean-point-in-polygon": "^6.5.0", @@ -78,6 +79,7 @@ "react-native-tab-view": "^3.3.4", "react-native-vector-icons": "^10.0.0", "react-native-zip-archive": "^5.0.6", + "react-redux": "^9.0.4", "realm": "^11.10.2" }, "devDependencies": {