diff --git a/dev-client/__tests__/integration/RestrictByRequirements.test.tsx b/dev-client/__tests__/integration/ScreenDataRequirements.test.tsx
similarity index 73%
rename from dev-client/__tests__/integration/RestrictByRequirements.test.tsx
rename to dev-client/__tests__/integration/ScreenDataRequirements.test.tsx
index 1a82a09e4..3547f276f 100644
--- a/dev-client/__tests__/integration/RestrictByRequirements.test.tsx
+++ b/dev-client/__tests__/integration/ScreenDataRequirements.test.tsx
@@ -19,9 +19,9 @@ import {Text} from 'react-native';
import {render} from '@testing/integration/utils';
-import {RestrictByRequirements} from 'terraso-mobile-client/components/dataRequirements/RestrictByRequirements';
+import {ScreenDataRequirements} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
-describe('RestrictByRequrements', () => {
+describe('ScreenDataRequirements', () => {
test('renders children and triggers no actions when required data exists', () => {
let thingsDone = '';
const requirements = [
@@ -37,9 +37,9 @@ describe('RestrictByRequrements', () => {
];
const {queryByText} = render(
-
+
{() => Hello world}
- ,
+ ,
);
expect(queryByText('Hello world')).toBeTruthy();
@@ -64,9 +64,9 @@ describe('RestrictByRequrements', () => {
];
const {queryByText} = render(
-
+
{() => Hello world}
- ,
+ ,
);
expect(queryByText('Hello world')).toBeNull();
@@ -91,12 +91,31 @@ describe('RestrictByRequrements', () => {
];
const {queryByText} = render(
-
+
{() => Hello world}
- ,
+ ,
);
expect(queryByText('Hello world')).toBeNull();
expect(thingsDone).toEqual('null');
});
+
+ test('renders children and triggers no action when required data exists, even if it is a boolean equal to false', () => {
+ let thingsDone = '';
+ const requirements = [
+ {
+ data: false,
+ doIfMissing: () => (thingsDone += 'false'),
+ },
+ ];
+
+ const {queryByText} = render(
+
+ {() => Hello world}
+ ,
+ );
+
+ expect(queryByText('Hello world')).toBeTruthy();
+ expect(thingsDone).toEqual('');
+ });
});
diff --git a/dev-client/src/components/dataRequirements/RestrictByRequirements.tsx b/dev-client/src/components/dataRequirements/ScreenDataRequirements.tsx
similarity index 62%
rename from dev-client/src/components/dataRequirements/RestrictByRequirements.tsx
rename to dev-client/src/components/dataRequirements/ScreenDataRequirements.tsx
index 4e541f01d..1792deceb 100644
--- a/dev-client/src/components/dataRequirements/RestrictByRequirements.tsx
+++ b/dev-client/src/components/dataRequirements/ScreenDataRequirements.tsx
@@ -15,7 +15,7 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-import {useEffect} from 'react';
+import {useEffect, useMemo} from 'react';
type Requirement = {
data: any;
@@ -49,11 +49,31 @@ type Props = {
children: () => React.ReactNode;
};
-export const RestrictByRequirements = ({requirements, children}: Props) => {
+/*
+ * This is intended to wrap components (mostly screens as of 2024-12) so they only render
+ * if required data is truthy. This prevents screens from breaking if, for example, a pull
+ * happens that deletes data that is required to view the screen.
+ */
+export const ScreenDataRequirements = ({requirements, children}: Props) => {
const requiredDataExists = useRequiredData(requirements);
-
if (!requiredDataExists) {
return null;
}
return <>{children()}>;
};
+
+/*
+ * I believe this is not strictly necessary; if a screen is re-rendering, the ScreenDataRequirements component
+ * will re-render regardless of if its props change (unless memoized)
+ */
+export const useMemoizedRequirements = (
+ requirementsAsNewObj: Requirement[],
+) => {
+ const deps = requirementsAsNewObj.flatMap(r => [r.data, r.doIfMissing]);
+ return useMemo(() => {
+ return requirementsAsNewObj;
+ // The linter doesn't like the dynamic dependency list, but the useMemo should only depend on the values of the required data
+ // and missing data handlers. If it depends on the list object, it's re-created every render.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, deps);
+};
diff --git a/dev-client/src/components/dataRequirements/handleMissingData.ts b/dev-client/src/components/dataRequirements/handleMissingData.ts
index b96d925e9..4e32776c0 100644
--- a/dev-client/src/components/dataRequirements/handleMissingData.ts
+++ b/dev-client/src/components/dataRequirements/handleMissingData.ts
@@ -21,7 +21,7 @@ import {isFlagEnabled} from 'terraso-mobile-client/config/featureFlags';
import {useSyncNotificationContext} from 'terraso-mobile-client/context/SyncNotificationContext';
import {useNavigation} from 'terraso-mobile-client/navigation/hooks/useNavigation';
-export const useHandleMissingSite = () => {
+export const useNavToBottomTabsAndShowSyncError = () => {
const navigation = useNavigation();
const syncNotifications = useSyncNotificationContext();
@@ -32,3 +32,15 @@ export const useHandleMissingSite = () => {
}
}, [navigation, syncNotifications]);
};
+
+export const usePopNavigationAndShowSyncError = () => {
+ const navigation = useNavigation();
+ const syncNotifications = useSyncNotificationContext();
+
+ return useCallback(() => {
+ navigation.pop();
+ if (isFlagEnabled('FF_offline')) {
+ syncNotifications.showError();
+ }
+ }, [navigation, syncNotifications]);
+};
diff --git a/dev-client/src/navigation/screenDefinitions.tsx b/dev-client/src/navigation/screenDefinitions.tsx
index ae9acd2af..22c1f8c92 100644
--- a/dev-client/src/navigation/screenDefinitions.tsx
+++ b/dev-client/src/navigation/screenDefinitions.tsx
@@ -28,9 +28,10 @@ import {CreateProjectScreen} from 'terraso-mobile-client/screens/CreateProjectSc
import {CreateSiteScreen} from 'terraso-mobile-client/screens/CreateSiteScreen/CreateSiteScreen';
import {DeleteAccountScreen} from 'terraso-mobile-client/screens/DeleteAccountScreen/DeleteAccountScreen';
import {EditPinnedNoteScreen} from 'terraso-mobile-client/screens/EditPinnedNoteScreen';
-import {LocationSoilIdScreen} from 'terraso-mobile-client/screens/LocationScreens/LocationSoilIdScreen';
+import {SiteLocationSoilIdScreen} from 'terraso-mobile-client/screens/LocationScreens/SiteLocationSoilIdScreen';
import {SiteTabsScreen} from 'terraso-mobile-client/screens/LocationScreens/SiteTabsScreen';
import {TemporaryLocationScreen} from 'terraso-mobile-client/screens/LocationScreens/TemporaryLocationScreen';
+import {TemporaryLocationSoilIdScreen} from 'terraso-mobile-client/screens/LocationScreens/TemporaryLocationSoilIdScreen';
import {LoginScreen} from 'terraso-mobile-client/screens/LoginScreen';
import {ManageTeamMemberScreen} from 'terraso-mobile-client/screens/ManageTeamMemberScreen';
import {ProjectListScreen} from 'terraso-mobile-client/screens/ProjectListScreen/ProjectListScreen';
@@ -59,6 +60,8 @@ import {TextureScreen} from 'terraso-mobile-client/screens/SoilScreen/TextureScr
import {UserSettingsScreen} from 'terraso-mobile-client/screens/UserSettingsScreen/UserSettingsScreen';
import {WelcomeScreen} from 'terraso-mobile-client/screens/WelcomeScreen';
+SiteLocationSoilIdScreen;
+
export const bottomTabScreensDefinitions = {
PROJECT_LIST: ProjectListScreen,
SITES: SitesScreen,
@@ -75,7 +78,8 @@ export const screenDefinitions = {
CREATE_SITE: CreateSiteScreen,
TEMP_LOCATION: TemporaryLocationScreen,
SITE_TABS: SiteTabsScreen,
- LOCATION_SOIL_ID: LocationSoilIdScreen,
+ SITE_LOCATION_SOIL_ID: SiteLocationSoilIdScreen,
+ TEMPORARY_LOCATION_SOIL_ID: TemporaryLocationSoilIdScreen,
SITE_SETTINGS: SiteSettingsScreen,
SITE_TEAM_SETTINGS: SiteTeamSettingsScreen,
ADD_USER_PROJECT: AddUserToProjectScreen,
diff --git a/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectRoleScreen.tsx b/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectRoleScreen.tsx
index bc7496b86..9772992d9 100644
--- a/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectRoleScreen.tsx
+++ b/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectRoleScreen.tsx
@@ -24,6 +24,14 @@ import {Button} from 'native-base';
import {ProjectRole} from 'terraso-client-shared/project/projectTypes';
import {ScreenContentSection} from 'terraso-mobile-client/components/content/ScreenContentSection';
+import {
+ useNavToBottomTabsAndShowSyncError,
+ usePopNavigationAndShowSyncError,
+} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Box,
Column,
@@ -69,48 +77,59 @@ export const AddUserToProjectRoleScreen = ({projectId, userId}: Props) => {
navigation.dispatch(TabActions.jumpTo(TabRoutes.TEAM));
}, [dispatch, projectId, user, selectedRole, navigation]);
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const handleMissingUser = usePopNavigationAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ {data: user, doIfMissing: handleMissingUser},
+ ]);
+
return (
- }>
-
-
-
-
-
+
+ {() => (
+ }>
+
+
+
+
+
-
+
-
-
- {/* FYI: The 1px border is to visually match the size of the outline
+
+
+ {/* FYI: The 1px border is to visually match the size of the outline
variant, which appears to be 1px bigger than the solid variant due
to its border. */}
-
-
-
-
-
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectScreen.tsx b/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectScreen.tsx
index e0e9baede..1957d8d23 100644
--- a/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectScreen.tsx
+++ b/dev-client/src/screens/AddUserToProjectScreen/AddUserToProjectScreen.tsx
@@ -18,11 +18,17 @@
import {useTranslation} from 'react-i18next';
import {ScreenContentSection} from 'terraso-mobile-client/components/content/ScreenContentSection';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Box, Text} from 'terraso-mobile-client/components/NativeBaseAdapters';
import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar';
import {AddTeamMemberForm} from 'terraso-mobile-client/screens/AddUserToProjectScreen/components/AddTeamMemberForm';
import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold';
import {useSelector} from 'terraso-mobile-client/store';
+import {selectProject} from 'terraso-mobile-client/store/selectors';
type Props = {
projectId: string;
@@ -31,22 +37,29 @@ type Props = {
export const AddUserToProjectScreen = ({projectId}: Props) => {
const {t} = useTranslation();
- const projectName = useSelector(
- state => state.project.projects[projectId]?.name,
- );
+ const project = useSelector(selectProject(projectId));
// FYI: There was previously a mechanism to enter emails individually, but set roles at the same time.
// This was replaced, but we could refer back to `userRecord` in previous versions if we ever end up
// wanting to add multiple users at the same time.
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
+
return (
- }>
-
- {t('projects.add_user.help_text')}
-
-
-
-
-
+
+ {() => (
+ }>
+
+ {t('projects.add_user.help_text')}
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/CreateSiteScreen/CreateSiteScreen.tsx b/dev-client/src/screens/CreateSiteScreen/CreateSiteScreen.tsx
index 480b66629..0e6e9de4e 100644
--- a/dev-client/src/screens/CreateSiteScreen/CreateSiteScreen.tsx
+++ b/dev-client/src/screens/CreateSiteScreen/CreateSiteScreen.tsx
@@ -22,7 +22,6 @@ import {Coords} from 'terraso-client-shared/types';
import {ScreenCloseButton} from 'terraso-mobile-client/components/buttons/icons/appBar/ScreenCloseButton';
import {addSite} from 'terraso-mobile-client/model/site/siteGlobalReducer';
-import {fetchSitesForProject} from 'terraso-mobile-client/model/site/siteSlice';
import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar';
import {CreateSiteView} from 'terraso-mobile-client/screens/CreateSiteScreen/components/CreateSiteView';
import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold';
@@ -32,9 +31,6 @@ type Props =
| {
coords: Coords;
}
- | {
- projectId: string;
- }
| {
elevation: number;
}
@@ -52,9 +48,6 @@ export const CreateSiteScreen = (props: Props = {}) => {
console.error(result.payload.parsedErrors);
return;
}
- if (input.projectId) {
- dispatch(fetchSitesForProject(input.projectId));
- }
return result.payload;
},
[dispatch],
@@ -66,7 +59,6 @@ export const CreateSiteScreen = (props: Props = {}) => {
AppBar={} />}>
diff --git a/dev-client/src/screens/EditPinnedNoteScreen.tsx b/dev-client/src/screens/EditPinnedNoteScreen.tsx
index 3f63a289a..7aa4bb30c 100644
--- a/dev-client/src/screens/EditPinnedNoteScreen.tsx
+++ b/dev-client/src/screens/EditPinnedNoteScreen.tsx
@@ -21,6 +21,11 @@ import {Keyboard} from 'react-native';
import {ProjectUpdateMutationInput} from 'terraso-client-shared/graphqlSchema/graphql';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Box,
Column,
@@ -51,6 +56,7 @@ export const EditPinnedNoteScreen = ({projectId}: Props) => {
try {
const projectInput: ProjectUpdateMutationInput = {
id: project.id,
+ // FYI: "pinned notes" historically have been called "site instructions" or "project instructions"
siteInstructions: content,
};
await dispatch(updateProject(projectInput));
@@ -66,23 +72,32 @@ export const EditPinnedNoteScreen = ({projectId}: Props) => {
await handleUpdateProject({content: ''});
};
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
+
return (
-
- {formikProps => (
-
-
- {t('projects.inputs.instructions.title')}
-
-
-
-
-
+
+ {() => (
+
+ {formikProps => (
+
+
+ {t('projects.inputs.instructions.title')}
+
+
+
+
+
+ )}
+
)}
-
+
);
};
diff --git a/dev-client/src/screens/LocationScreens/LocationDashboardContent.tsx b/dev-client/src/screens/LocationScreens/LocationDashboardContent.tsx
index d96db4c17..ea150e7db 100644
--- a/dev-client/src/screens/LocationScreens/LocationDashboardContent.tsx
+++ b/dev-client/src/screens/LocationScreens/LocationDashboardContent.tsx
@@ -79,7 +79,11 @@ export const LocationDashboardContent = ({site, coords, elevation}: Props) => {
);
const onExploreDataPress = useCallback(() => {
- navigation.navigate('LOCATION_SOIL_ID', {siteId: site?.id, coords});
+ if (site?.id) {
+ navigation.navigate('SITE_LOCATION_SOIL_ID', {siteId: site.id, coords});
+ } else {
+ navigation.navigate('TEMPORARY_LOCATION_SOIL_ID', {coords});
+ }
}, [navigation, site, coords]);
const onSitePrivacyChanged = useCallback(
diff --git a/dev-client/src/screens/LocationScreens/SiteDashboardScreen.tsx b/dev-client/src/screens/LocationScreens/SiteDashboardScreen.tsx
index 9f1356643..a2de34571 100644
--- a/dev-client/src/screens/LocationScreens/SiteDashboardScreen.tsx
+++ b/dev-client/src/screens/LocationScreens/SiteDashboardScreen.tsx
@@ -17,20 +17,35 @@
import {Coords} from 'terraso-client-shared/types';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {LocationDashboardContent} from 'terraso-mobile-client/screens/LocationScreens/LocationDashboardContent';
import {useSelector} from 'terraso-mobile-client/store';
+import {selectSite} from 'terraso-mobile-client/store/selectors';
type Props = {
siteId: string;
};
export const SiteDashboardScreen = ({siteId}: Props) => {
- const site = useSelector(state => state.site.sites[siteId]);
+ const site = useSelector(selectSite(siteId));
+
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
return (
-
+
+ {() => (
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/LocationScreens/LocationSoilIdScreen.tsx b/dev-client/src/screens/LocationScreens/SiteLocationSoilIdScreen.tsx
similarity index 58%
rename from dev-client/src/screens/LocationScreens/LocationSoilIdScreen.tsx
rename to dev-client/src/screens/LocationScreens/SiteLocationSoilIdScreen.tsx
index c3427f5c1..37ca01e1c 100644
--- a/dev-client/src/screens/LocationScreens/LocationSoilIdScreen.tsx
+++ b/dev-client/src/screens/LocationScreens/SiteLocationSoilIdScreen.tsx
@@ -20,10 +20,13 @@ import {ScrollView} from 'react-native-gesture-handler';
import {Coords} from 'terraso-client-shared/types';
-import {Box} from 'terraso-mobile-client/components/NativeBaseAdapters';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {SiteRoleContextProvider} from 'terraso-mobile-client/context/SiteRoleContext';
import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar';
-import {CreateSiteButton} from 'terraso-mobile-client/screens/LocationScreens/components/CreateSiteButton';
import {SiteDataSection} from 'terraso-mobile-client/screens/LocationScreens/components/soilId/SiteDataSection';
import {SoilIdDescriptionSection} from 'terraso-mobile-client/screens/LocationScreens/components/soilId/SoilIdDescriptionSection';
import {SoilIdMatchesSection} from 'terraso-mobile-client/screens/LocationScreens/components/soilId/SoilIdMatchesSection';
@@ -32,42 +35,39 @@ import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold';
import {useSelector} from 'terraso-mobile-client/store';
import {selectSite} from 'terraso-mobile-client/store/selectors';
-type Props = {
- siteId?: string;
+type SiteProps = {
+ siteId: string;
coords: Coords;
};
-export const LocationSoilIdScreen = ({siteId, coords}: Props) => {
+export const SiteLocationSoilIdScreen = ({siteId, coords}: SiteProps) => {
const {t} = useTranslation();
- const isSite = !!siteId;
- const site = useSelector(state =>
- isSite ? selectSite(siteId)(state) : undefined,
- );
+ const site = useSelector(state => selectSite(siteId)(state));
+
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
return (
-
- }>
-
- {isSite ? (
-
- ) : (
- <>>
- )}
-
-
- {isSite ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
+
+ {() => (
+
+ }>
+
+
+
+
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/LocationScreens/SiteTabsScreen.tsx b/dev-client/src/screens/LocationScreens/SiteTabsScreen.tsx
index 9d1479043..a04168c27 100644
--- a/dev-client/src/screens/LocationScreens/SiteTabsScreen.tsx
+++ b/dev-client/src/screens/LocationScreens/SiteTabsScreen.tsx
@@ -19,8 +19,11 @@ import {useMemo} from 'react';
import {useTranslation} from 'react-i18next';
import {AppBarIconButton} from 'terraso-mobile-client/components/buttons/icons/appBar/AppBarIconButton';
-import {useHandleMissingSite} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
-import {RestrictByRequirements} from 'terraso-mobile-client/components/dataRequirements/RestrictByRequirements';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {SiteRoleContextProvider} from 'terraso-mobile-client/context/SiteRoleContext';
import {isSiteManager} from 'terraso-mobile-client/model/permissions/permissions';
import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar';
@@ -51,8 +54,10 @@ export const SiteTabsScreen = (props: Props) => {
const site = useSelector(state => selectSite(siteId)(state));
const userRole = useSelector(state => selectUserRoleSite(state, siteId));
- const handleMissingSite = useHandleMissingSite();
- const requirements = [{data: site, doIfMissing: handleMissingSite}];
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
const appBarRightButton = useMemo(() => {
// display nothing if user does not own the site / is not manager
@@ -70,7 +75,7 @@ export const SiteTabsScreen = (props: Props) => {
}, [siteId, navigation, userRole]);
return (
-
+
{() => (
{
)}
-
+
);
};
diff --git a/dev-client/src/screens/LocationScreens/TemporaryLocationSoilIdScreen.tsx b/dev-client/src/screens/LocationScreens/TemporaryLocationSoilIdScreen.tsx
new file mode 100644
index 000000000..1dbbe15ff
--- /dev/null
+++ b/dev-client/src/screens/LocationScreens/TemporaryLocationSoilIdScreen.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2024 Technology Matters
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see https://www.gnu.org/licenses/.
+ */
+
+import {useTranslation} from 'react-i18next';
+import {ScrollView} from 'react-native-gesture-handler';
+
+import {Coords} from 'terraso-client-shared/types';
+
+import {Box} from 'terraso-mobile-client/components/NativeBaseAdapters';
+import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar';
+import {CreateSiteButton} from 'terraso-mobile-client/screens/LocationScreens/components/CreateSiteButton';
+import {SoilIdDescriptionSection} from 'terraso-mobile-client/screens/LocationScreens/components/soilId/SoilIdDescriptionSection';
+import {SoilIdMatchesSection} from 'terraso-mobile-client/screens/LocationScreens/components/soilId/SoilIdMatchesSection';
+import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold';
+
+type TempLocationProps = {
+ coords: Coords;
+};
+
+export const TemporaryLocationSoilIdScreen = ({coords}: TempLocationProps) => {
+ const {t} = useTranslation();
+
+ return (
+ }>
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/dev-client/src/screens/ManageTeamMemberScreen.tsx b/dev-client/src/screens/ManageTeamMemberScreen.tsx
index e725b230e..e59dbd5df 100644
--- a/dev-client/src/screens/ManageTeamMemberScreen.tsx
+++ b/dev-client/src/screens/ManageTeamMemberScreen.tsx
@@ -25,6 +25,14 @@ import {ProjectRole} from 'terraso-client-shared/project/projectTypes';
import {ScreenCloseButton} from 'terraso-mobile-client/components/buttons/icons/appBar/ScreenCloseButton';
import {ScreenContentSection} from 'terraso-mobile-client/components/content/ScreenContentSection';
+import {
+ useNavToBottomTabsAndShowSyncError,
+ usePopNavigationAndShowSyncError,
+} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Icon} from 'terraso-mobile-client/components/icons/Icon';
import {ConfirmModal} from 'terraso-mobile-client/components/modals/ConfirmModal';
import {
@@ -81,53 +89,64 @@ export const ManageTeamMemberScreen = ({
navigation.pop();
}, [dispatch, projectId, userId, selectedRole, navigation]);
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const handleMissingUser = usePopNavigationAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ {data: user, doIfMissing: handleMissingUser},
+ ]);
+
return (
- } />
- }>
-
-
-
-
-
-
-
-
-
- (
- }>
- {t('projects.manage_member.remove')}
-
- )}
- title={t('projects.manage_member.confirm_removal_title')}
- body={t('projects.manage_member.confirm_removal_body')}
- actionName={t('projects.manage_member.confirm_removal_action')}
- handleConfirm={removeMembership}
- />
-
- {t('projects.manage_member.remove_help')}
-
-
-
-
-
-
-
-
+
+ {() => (
+ } />
+ }>
+
+
+
+
+
+
+
+
+
+ (
+ }>
+ {t('projects.manage_member.remove')}
+
+ )}
+ title={t('projects.manage_member.confirm_removal_title')}
+ body={t('projects.manage_member.confirm_removal_body')}
+ actionName={t('projects.manage_member.confirm_removal_action')}
+ handleConfirm={removeMembership}
+ />
+
+ {t('projects.manage_member.remove_help')}
+
+
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/ProjectInputScreen/ProjectInputScreen.tsx b/dev-client/src/screens/ProjectInputScreen/ProjectInputScreen.tsx
index 220e73ccf..3b99bb1d1 100644
--- a/dev-client/src/screens/ProjectInputScreen/ProjectInputScreen.tsx
+++ b/dev-client/src/screens/ProjectInputScreen/ProjectInputScreen.tsx
@@ -25,6 +25,11 @@ import {Button, Fab} from 'native-base';
import {Accordion} from 'terraso-mobile-client/components/Accordion';
import {HelpContentSpacer} from 'terraso-mobile-client/components/content/HelpContentSpacer';
import {DataPrivacyInfoButton} from 'terraso-mobile-client/components/content/info/privacy/DataPrivacyInfoButton';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Icon} from 'terraso-mobile-client/components/icons/Icon';
import {
Box,
@@ -45,6 +50,7 @@ import {useNavigation} from 'terraso-mobile-client/navigation/hooks/useNavigatio
import {RequiredDataSettings} from 'terraso-mobile-client/screens/ProjectInputScreen/RequiredDataSettings';
import {SoilPitSettings} from 'terraso-mobile-client/screens/ProjectInputScreen/SoilPitSettings';
import {useDispatch, useSelector} from 'terraso-mobile-client/store';
+import {selectProject} from 'terraso-mobile-client/store/selectors';
type Props = NativeStackScreenProps;
@@ -55,7 +61,7 @@ export const ProjectInputScreen = ({
}: Props) => {
const {t} = useTranslation();
const navigation = useNavigation();
- const project = useSelector(state => state.project.projects[projectId]);
+ const project = useSelector(selectProject(projectId));
const dispatch = useDispatch();
const onEditPinnedNote = useCallback(() => {
@@ -79,78 +85,90 @@ export const ProjectInputScreen = ({
const allowEditing = useMemo(() => userRole === 'MANAGER', [userRole]);
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
+
return (
-
-
-
-
-
-
- {t('site.dashboard.privacy')}
-
-
-
-
- }
- options={{
- PUBLIC: {text: t('privacy.public.title')},
- PRIVATE: {text: t('privacy.private.title')},
- }}
- groupProps={{
- name: 'project-privacy',
- onChange: onProjectPrivacyChanged,
- value: project?.privacy,
- ml: '0',
- variant: 'oneLine',
- }}
- allDisabled={!allowEditing}
- />
-
+
+ {() => (
+
+
+
+
+
+
+ {t('site.dashboard.privacy')}
+
+
+
+
+ }
+ options={{
+ PUBLIC: {text: t('privacy.public.title')},
+ PRIVATE: {text: t('privacy.private.title')},
+ }}
+ groupProps={{
+ name: 'project-privacy',
+ onChange: onProjectPrivacyChanged,
+ value: project?.privacy,
+ ml: '0',
+ variant: 'oneLine',
+ }}
+ allDisabled={!allowEditing}
+ />
+
+
+ {t('projects.inputs.instructions.title')}
+ {t('projects.inputs.instructions.description')}
+ }>
+ {t('projects.inputs.instructions.add_label')}
+
+
+
+
+ {t('soil.pit')}
+
+ }>
+
+
+
+
+ {t('soil.project_settings.required_data_title')}
+
+ }>
+
+
+
- {t('projects.inputs.instructions.title')}
- {t('projects.inputs.instructions.description')}
- }>
- {t('projects.inputs.instructions.add_label')}
-
+ onSave()}
+ textTransform="uppercase"
+ label={t('general.save_fab')}
+ renderInPortal={false}
+ />
-
-
- {t('soil.pit')}
-
- }>
-
-
-
-
- {t('soil.project_settings.required_data_title')}
-
- }>
-
-
-
-
- onSave()}
- textTransform="uppercase"
- label={t('general.save_fab')}
- renderInPortal={false}
- />
-
-
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/ProjectListScreen/ProjectListScreen.tsx b/dev-client/src/screens/ProjectListScreen/ProjectListScreen.tsx
index 89ebe11b6..c6e62753f 100644
--- a/dev-client/src/screens/ProjectListScreen/ProjectListScreen.tsx
+++ b/dev-client/src/screens/ProjectListScreen/ProjectListScreen.tsx
@@ -41,20 +41,21 @@ import {OfflineMessageBox} from 'terraso-mobile-client/screens/LocationScreens/c
import {ProjectList} from 'terraso-mobile-client/screens/ProjectListScreen/components/ProjectList';
import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold';
import {useSelector} from 'terraso-mobile-client/store';
-import {selectProjectUserRolesMap} from 'terraso-mobile-client/store/selectors';
+import {
+ selectProjects,
+ selectProjectUserRolesMap,
+} from 'terraso-mobile-client/store/selectors';
import {equals, searchText} from 'terraso-mobile-client/util';
const SORT_OPTIONS = ['nameAsc', 'nameDesc', 'lastModAsc', 'lastModDesc'];
export const ProjectListScreen = () => {
- const allProjects = useSelector(state => state.project.projects);
+ const allProjects = useSelector(selectProjects);
const activeProjects = useMemo(
() => Object.values(allProjects).filter(project => !project.archived),
[allProjects],
);
- const projectRoleLookup = useSelector(state =>
- selectProjectUserRolesMap(state),
- );
+ const projectRoleLookup = useSelector(selectProjectUserRolesMap);
const {t} = useTranslation();
const navigation = useNavigation();
diff --git a/dev-client/src/screens/ProjectSettingsScreen.tsx b/dev-client/src/screens/ProjectSettingsScreen.tsx
index 045df3548..feab0f0f1 100644
--- a/dev-client/src/screens/ProjectSettingsScreen.tsx
+++ b/dev-client/src/screens/ProjectSettingsScreen.tsx
@@ -24,6 +24,11 @@ import {ScrollView} from 'native-base';
import {ProjectUpdateMutationInput} from 'terraso-client-shared/graphqlSchema/graphql';
import DeleteButton from 'terraso-mobile-client/components/buttons/DeleteButton';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {ConfirmModal} from 'terraso-mobile-client/components/modals/ConfirmModal';
import {Column} from 'terraso-mobile-client/components/NativeBaseAdapters';
import {RestrictByProjectRole} from 'terraso-mobile-client/components/restrictions/RestrictByRole';
@@ -50,7 +55,8 @@ export function ProjectSettingsScreen({
}: Props) {
const {t} = useTranslation();
const dispatch = useDispatch();
- const {name, description, privacy} = useSelector(selectProject(projectId));
+ const project = useSelector(selectProject(projectId));
+ const {name, description, privacy} = project;
const onSubmit = async (values: Omit) => {
await dispatch(updateProject({...values, id: projectId, privacy}));
@@ -65,38 +71,47 @@ export function ProjectSettingsScreen({
const userRole = useProjectRoleContext();
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
+
return (
-
-
-
-
- (
-
+ {() => (
+
+
+
+
+ (
+
+ )}
/>
- )}
- />
-
-
-
+
+
+
+ )}
+
);
}
diff --git a/dev-client/src/screens/ProjectSitesScreen.tsx b/dev-client/src/screens/ProjectSitesScreen.tsx
index c143addc6..34e045947 100644
--- a/dev-client/src/screens/ProjectSitesScreen.tsx
+++ b/dev-client/src/screens/ProjectSitesScreen.tsx
@@ -29,6 +29,11 @@ import {Site} from 'terraso-client-shared/site/siteTypes';
import {normalizeText} from 'terraso-client-shared/utils';
import {IconButton} from 'terraso-mobile-client/components/buttons/icons/IconButton';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
ListFilterModal,
ListFilterProvider,
@@ -58,6 +63,7 @@ import {
} from 'terraso-mobile-client/navigation/constants';
import {RootStackScreenProps} from 'terraso-mobile-client/navigation/types';
import {AppState, useDispatch, useSelector} from 'terraso-mobile-client/store';
+import {selectProject} from 'terraso-mobile-client/store/selectors';
import {theme} from 'terraso-mobile-client/theme';
import {searchText} from 'terraso-mobile-client/util';
@@ -259,31 +265,41 @@ export function ProjectSitesScreen({
);
+ const project = useSelector(selectProject(projectId));
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
+
return (
-
- {isEmpty && (
- <>
- {t('projects.sites.empty_viewer')}
+
+ {() => (
+
+ {isEmpty && (
+ <>
+ {t('projects.sites.empty_viewer')}
+
+ {t('projects.sites.empty_contributor')}
+
+ >
+ )}
- {t('projects.sites.empty_contributor')}
+
- >
+ {!isEmpty && full}
+
)}
-
-
-
- {!isEmpty && full}
-
+
);
}
diff --git a/dev-client/src/screens/ProjectTeamScreen/ProjectTeamScreen.tsx b/dev-client/src/screens/ProjectTeamScreen/ProjectTeamScreen.tsx
index 4a24826b1..ff3399090 100644
--- a/dev-client/src/screens/ProjectTeamScreen/ProjectTeamScreen.tsx
+++ b/dev-client/src/screens/ProjectTeamScreen/ProjectTeamScreen.tsx
@@ -23,6 +23,11 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack';
import {ProjectMembership} from 'terraso-client-shared/project/projectTypes';
import {AddButton} from 'terraso-mobile-client/components/AddButton';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Box,
Column,
@@ -39,7 +44,10 @@ import {
import {useNavigation} from 'terraso-mobile-client/navigation/hooks/useNavigation';
import {UserList} from 'terraso-mobile-client/screens/ProjectTeamScreen/components/UserList';
import {useDispatch, useSelector} from 'terraso-mobile-client/store';
-import {selectProjectMembershipsWithUsers} from 'terraso-mobile-client/store/selectors';
+import {
+ selectProject,
+ selectProjectMembershipsWithUsers,
+} from 'terraso-mobile-client/store/selectors';
type Props = NativeStackScreenProps;
@@ -89,33 +97,47 @@ export const ProjectTeamScreen = ({route}: Props) => {
[navigation, route.params.projectId, userRole],
);
+ const project = useSelector(selectProject(route.params.projectId));
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
+
return (
-
-
-
-
- navigation.navigate('ADD_USER_PROJECT', {
- projectId: route.params.projectId,
- }),
- }}
- />
-
-
-
- {t('projects.team.manage_team')}
-
-
-
-
-
+
+ {() => (
+
+
+
+
+ navigation.navigate('ADD_USER_PROJECT', {
+ projectId: route.params.projectId,
+ }),
+ }}
+ />
+
+
+
+ {t('projects.team.manage_team')}
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/ProjectViewScreen.tsx b/dev-client/src/screens/ProjectViewScreen.tsx
index 07fa6f00b..dc59ed261 100644
--- a/dev-client/src/screens/ProjectViewScreen.tsx
+++ b/dev-client/src/screens/ProjectViewScreen.tsx
@@ -15,6 +15,11 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {ProjectRoleContextProvider} from 'terraso-mobile-client/context/ProjectRoleContext';
import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar';
import {ProjectTabNavigator} from 'terraso-mobile-client/navigation/navigators/ProjectTabNavigator';
@@ -25,14 +30,23 @@ type Props = {projectId: string};
export const ProjectViewScreen = ({projectId}: Props) => {
const project = useSelector(state => state.project.projects[projectId]);
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
return (
-
- }
- BottomNavigation={null}>
-
-
-
+
+ {() => (
+
+ }
+ BottomNavigation={null}>
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SiteNotesScreen/AddSiteNoteScreen.tsx b/dev-client/src/screens/SiteNotesScreen/AddSiteNoteScreen.tsx
index 4311af023..c5f691aa7 100644
--- a/dev-client/src/screens/SiteNotesScreen/AddSiteNoteScreen.tsx
+++ b/dev-client/src/screens/SiteNotesScreen/AddSiteNoteScreen.tsx
@@ -21,6 +21,11 @@ import {Keyboard} from 'react-native';
import {SiteNoteAddMutationInput} from 'terraso-client-shared/graphqlSchema/graphql';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Box,
Column,
@@ -30,7 +35,8 @@ import {ScreenFormWrapper} from 'terraso-mobile-client/components/ScreenFormWrap
import {addSiteNote} from 'terraso-mobile-client/model/site/siteSlice';
import {useNavigation} from 'terraso-mobile-client/navigation/hooks/useNavigation';
import {SiteNoteForm} from 'terraso-mobile-client/screens/SiteNotesScreen/components/SiteNoteForm';
-import {useDispatch} from 'terraso-mobile-client/store';
+import {useDispatch, useSelector} from 'terraso-mobile-client/store';
+import {selectSite} from 'terraso-mobile-client/store/selectors';
type Props = {
siteId: string;
@@ -67,23 +73,33 @@ export const AddSiteNoteScreen = ({siteId}: Props) => {
navigation.pop();
};
+ const site = useSelector(selectSite(siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
-
- {formikProps => (
-
-
- {t('site.notes.add_title')}
-
-
-
-
-
+
+ {() => (
+
+ {formikProps => (
+
+
+ {t('site.notes.add_title')}
+
+
+
+
+
+ )}
+
)}
-
+
);
};
diff --git a/dev-client/src/screens/SiteNotesScreen/EditSiteNoteScreen.tsx b/dev-client/src/screens/SiteNotesScreen/EditSiteNoteScreen.tsx
index 5ded6811d..96ea1fe54 100644
--- a/dev-client/src/screens/SiteNotesScreen/EditSiteNoteScreen.tsx
+++ b/dev-client/src/screens/SiteNotesScreen/EditSiteNoteScreen.tsx
@@ -16,11 +16,14 @@
*/
import {useCallback} from 'react';
-import {ToastAndroid} from 'react-native';
-import {useHandleMissingSite} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
-import {RestrictByRequirements} from 'terraso-mobile-client/components/dataRequirements/RestrictByRequirements';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {isFlagEnabled} from 'terraso-mobile-client/config/featureFlags';
+import {useSyncNotificationContext} from 'terraso-mobile-client/context/SyncNotificationContext';
import {useNavigation} from 'terraso-mobile-client/navigation/hooks/useNavigation';
import {SiteTabName} from 'terraso-mobile-client/navigation/navigators/SiteTabNavigator';
import {EditSiteNoteContent} from 'terraso-mobile-client/screens/SiteNotesScreen/components/EditSiteNoteContent';
@@ -34,30 +37,30 @@ type Props = {
export const EditSiteNoteScreen = ({noteId, siteId}: Props) => {
const navigation = useNavigation();
+ const syncNotifications = useSyncNotificationContext();
const site = useSelector(state => selectSite(siteId)(state));
const note = site?.notes[noteId];
// TODO: Also handle the case where user no longer has permissions to edit notes
- const handleMissingSite = useHandleMissingSite();
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
const handleMissingSiteNote = useCallback(() => {
navigation.navigate('SITE_TABS', {
siteId: siteId,
initialTab: 'NOTES' as SiteTabName,
});
- // TODO: Decide design / how to show toasts / use en.json string
if (isFlagEnabled('FF_offline')) {
- ToastAndroid.show('Sorry, someone deleted that!', ToastAndroid.SHORT);
+ syncNotifications.showError();
}
- }, [navigation, siteId]);
- const requirements = [
+ }, [navigation, siteId, syncNotifications]);
+ const requirements = useMemoizedRequirements([
{data: site, doIfMissing: handleMissingSite},
{data: note, doIfMissing: handleMissingSiteNote},
- ];
+ ]);
return (
-
+
{() => }
-
+
);
};
diff --git a/dev-client/src/screens/SiteNotesScreen/ReadPinnedNoteScreen.tsx b/dev-client/src/screens/SiteNotesScreen/ReadPinnedNoteScreen.tsx
index 1a92fb881..20fa3eb44 100644
--- a/dev-client/src/screens/SiteNotesScreen/ReadPinnedNoteScreen.tsx
+++ b/dev-client/src/screens/SiteNotesScreen/ReadPinnedNoteScreen.tsx
@@ -19,6 +19,14 @@ import {useTranslation} from 'react-i18next';
import {Button, ScrollView, Spacer} from 'native-base';
+import {
+ useNavToBottomTabsAndShowSyncError,
+ usePopNavigationAndShowSyncError,
+} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Column,
Heading,
@@ -37,6 +45,7 @@ type Props = {
export const ReadPinnedNoteScreen = ({projectId}: Props) => {
const {t} = useTranslation();
const navigation = useNavigation();
+
const project = useSelector(selectProject(projectId));
const content = project?.siteInstructions;
@@ -44,26 +53,37 @@ export const ReadPinnedNoteScreen = ({projectId}: Props) => {
navigation.pop();
};
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const handleMissingPinnedNote = usePopNavigationAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ {data: content, doIfMissing: handleMissingPinnedNote},
+ ]);
+
return (
-
-
-
- {t('projects.inputs.instructions.screen_title')}
-
-
- {content}
-
-
-
-
-
-
-
+
+ {() => (
+
+
+
+ {t('projects.inputs.instructions.screen_title')}
+
+
+ {content}
+
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SiteNotesScreen/SiteNotesScreen.tsx b/dev-client/src/screens/SiteNotesScreen/SiteNotesScreen.tsx
index 4fca75531..217b51607 100644
--- a/dev-client/src/screens/SiteNotesScreen/SiteNotesScreen.tsx
+++ b/dev-client/src/screens/SiteNotesScreen/SiteNotesScreen.tsx
@@ -21,6 +21,11 @@ import {ScrollView} from 'react-native';
import {Button} from 'native-base';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Box,
Column,
@@ -35,11 +40,12 @@ import {OfflineMessageBox} from 'terraso-mobile-client/screens/LocationScreens/c
import {PinnedNoteCard} from 'terraso-mobile-client/screens/SiteNotesScreen/components/PinnedNoteCard';
import {SiteNoteCard} from 'terraso-mobile-client/screens/SiteNotesScreen/components/SiteNoteCard';
import {useSelector} from 'terraso-mobile-client/store';
+import {selectSite} from 'terraso-mobile-client/store/selectors';
export const SiteNotesScreen = ({siteId}: {siteId: string}) => {
const {t} = useTranslation();
const navigation = useNavigation();
- const site = useSelector(state => state.site.sites[siteId]);
+ const site = useSelector(selectSite(siteId));
const project = useSelector(state =>
site.projectId === undefined
? undefined
@@ -52,39 +58,48 @@ export const SiteNotesScreen = ({siteId}: {siteId: string}) => {
const isOffline = useIsOffline();
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
-
-
-
- {t('site.notes.title')}
-
-
- {project?.siteInstructions && }
-
-
- {isOffline ? (
-
- ) : (
-
- )}
-
-
+
+ {() => (
+
+
+
+ {t('site.notes.title')}
+
+
+ {project?.siteInstructions && }
+
+
+ {isOffline ? (
+
+ ) : (
+
+ )}
+
+
- {Object.values(site.notes).map(note => (
-
- ))}
- {site.notes && }
-
-
+ {Object.values(site.notes).map(note => (
+
+ ))}
+ {site.notes && }
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SiteSettingsScreen/SiteSettingsScreen.tsx b/dev-client/src/screens/SiteSettingsScreen/SiteSettingsScreen.tsx
index 7fd22ebcc..03de2bda9 100644
--- a/dev-client/src/screens/SiteSettingsScreen/SiteSettingsScreen.tsx
+++ b/dev-client/src/screens/SiteSettingsScreen/SiteSettingsScreen.tsx
@@ -22,6 +22,11 @@ import {PressableProps} from 'react-native';
import {Fab} from 'native-base';
import DeleteButton from 'terraso-mobile-client/components/buttons/DeleteButton';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {TextInput} from 'terraso-mobile-client/components/inputs/TextInput';
import {ConfirmModal} from 'terraso-mobile-client/components/modals/ConfirmModal';
import {
@@ -78,42 +83,49 @@ export const SiteSettingsScreen = ({siteId}: Props) => {
navigation.navigate('BOTTOM_TABS');
}, [dispatch, navigation, site]);
- if (!site) {
- return null;
- }
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
return (
- }>
-
- {t('site.dashboard.settings_title')}
+
+ {() => (
+ }>
+
+ {t('site.dashboard.settings_title')}
-
- {isOffline ? (
-
- ) : (
- }
- title={t('projects.sites.delete_site_modal.title')}
- body={t('projects.sites.delete_site_modal.body', {
- siteName: site.name,
- })}
- actionName={t('projects.sites.delete_site_modal.action_name')}
- handleConfirm={onDelete}
+
+ {isOffline ? (
+
+ ) : (
+ }
+ title={t('projects.sites.delete_site_modal.title')}
+ body={t('projects.sites.delete_site_modal.body', {
+ siteName: site.name,
+ })}
+ actionName={t('projects.sites.delete_site_modal.action_name')}
+ handleConfirm={onDelete}
+ />
+ )}
+
+
- )}
-
-
-
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SiteTeamSettingsScreen.tsx b/dev-client/src/screens/SiteTeamSettingsScreen.tsx
index 92b4d3d7f..2179d59c0 100644
--- a/dev-client/src/screens/SiteTeamSettingsScreen.tsx
+++ b/dev-client/src/screens/SiteTeamSettingsScreen.tsx
@@ -15,6 +15,11 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Text} from 'terraso-mobile-client/components/NativeBaseAdapters';
import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar';
import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold';
@@ -26,9 +31,19 @@ type Props = {
export const SiteTeamSettingsScreen = ({siteId}: Props) => {
const site = useSelector(state => state.site.sites[siteId]);
+
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
- }>
- Unimplemented team settings page
-
+
+ {() => (
+ }>
+ Unimplemented team settings page
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SiteTransferProjectScreen/SiteTransferProjectScreen.tsx b/dev-client/src/screens/SiteTransferProjectScreen/SiteTransferProjectScreen.tsx
index 4a59538c8..96e7cb095 100644
--- a/dev-client/src/screens/SiteTransferProjectScreen/SiteTransferProjectScreen.tsx
+++ b/dev-client/src/screens/SiteTransferProjectScreen/SiteTransferProjectScreen.tsx
@@ -22,6 +22,11 @@ import {ScrollView} from 'react-native';
import {Fab} from 'native-base';
import {Accordion} from 'terraso-mobile-client/components/Accordion';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {ConfirmModal} from 'terraso-mobile-client/components/modals/ConfirmModal';
import {Box, Text} from 'terraso-mobile-client/components/NativeBaseAdapters';
import {useTextSearch} from 'terraso-mobile-client/hooks/useTextSearch';
@@ -189,71 +194,80 @@ export const SiteTransferProjectScreen = ({projectId}: Props) => {
return navigation.pop();
}, [dispatch, navigation, projectId, checkedSites]);
+ const handleMissingProject = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: project, doIfMissing: handleMissingProject},
+ ]);
+
return (
- }>
-
-
- {listData.map(item => {
- const [projId, {projectName, sites: projectSites}] = item;
+
+ {() => (
+ }>
+
+
+ {listData.map(item => {
+ const [projId, {projectName, sites: projectSites}] = item;
- return (
-
- {projectName} ({projectSites.length})
-
- }
- boxProps={{
- mb: '2px',
- }}
- initiallyOpen={projectSites.length > 0}
- disableOpen={projectSites.length === 0}>
- {projectSites.length > 0 ? (
-
- ({
- label: siteName,
- id: siteId,
- checked:
- projState && projState[projId]
- ? projState[projId][siteId]
- : false,
- }))
- .sort((a, b) => a.label.localeCompare(b.label))}
- onChangeValue={onCheckboxChange}
- />
-
- ) : undefined}
-
- );
- })}
- (
-
+ {projectName} ({projectSites.length})
+
+ }
+ boxProps={{
+ mb: '2px',
+ }}
+ initiallyOpen={projectSites.length > 0}
+ disableOpen={projectSites.length === 0}>
+ {projectSites.length > 0 ? (
+
+ ({
+ label: siteName,
+ id: siteId,
+ checked:
+ projState && projState[projId]
+ ? projState[projId][siteId]
+ : false,
+ }))
+ .sort((a, b) => a.label.localeCompare(b.label))}
+ onChangeValue={onCheckboxChange}
+ />
+
+ ) : undefined}
+
+ );
+ })}
+ (
+
+ )}
+ title={t('projects.sites.transfer_site_modal.title')}
+ body={t('projects.sites.transfer_site_modal.body')}
+ actionName={t('projects.sites.transfer_site_modal.action_name')}
+ handleConfirm={onSubmit}
/>
- )}
- title={t('projects.sites.transfer_site_modal.title')}
- body={t('projects.sites.transfer_site_modal.body')}
- actionName={t('projects.sites.transfer_site_modal.action_name')}
- handleConfirm={onSubmit}
- />
-
-
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SitesScreen/SitesScreen.tsx b/dev-client/src/screens/SitesScreen/SitesScreen.tsx
index a92a90a14..0b5640ef2 100644
--- a/dev-client/src/screens/SitesScreen/SitesScreen.tsx
+++ b/dev-client/src/screens/SitesScreen/SitesScreen.tsx
@@ -55,20 +55,22 @@ import {
} from 'terraso-mobile-client/screens/SitesScreen/SitesScreenCallout';
import {getSitesScreenFilters} from 'terraso-mobile-client/screens/SitesScreen/utils/sitesScreenFilters';
import {useDispatch, useSelector} from 'terraso-mobile-client/store';
-import {selectSitesAndUserRoles} from 'terraso-mobile-client/store/selectors';
+import {
+ selectCurrentUserID,
+ selectSites,
+ selectSitesAndUserRoles,
+} from 'terraso-mobile-client/store/selectors';
export const SitesScreen = memo(() => {
const siteListBottomSheetRef = useRef(null);
const [mapStyleURL, setMapStyleURL] = useState(Mapbox.StyleURL.Street);
const [calloutState, setCalloutState] = useState(noneCallout());
- const currentUserID = useSelector(
- state => state.account.currentUser?.data?.id,
- );
- const sites = useSelector(state => state.site.sites);
+ const currentUserID = useSelector(selectCurrentUserID);
+ const sites = useSelector(selectSites);
const siteList = useMemo(() => Object.values(sites), [sites]);
const dispatch = useDispatch();
const mapRef = useRef(null);
- const siteProjectRoles = useSelector(state => selectSitesAndUserRoles(state));
+ const siteProjectRoles = useSelector(selectSitesAndUserRoles);
const sitesScreenContext = useContext(SitesScreenContext);
const showSiteOnMap = useCallback(
diff --git a/dev-client/src/screens/SlopeScreen/SlopeMeterScreen.tsx b/dev-client/src/screens/SlopeScreen/SlopeMeterScreen.tsx
index ef138a6b5..4451d98a3 100644
--- a/dev-client/src/screens/SlopeScreen/SlopeMeterScreen.tsx
+++ b/dev-client/src/screens/SlopeScreen/SlopeMeterScreen.tsx
@@ -28,6 +28,11 @@ import {BigCloseButton} from 'terraso-mobile-client/components/buttons/icons/com
import {InfoButton} from 'terraso-mobile-client/components/buttons/icons/common/InfoButton';
import {HelpContentSpacer} from 'terraso-mobile-client/components/content/HelpContentSpacer';
import {TranslatedHeading} from 'terraso-mobile-client/components/content/typography/TranslatedHeading';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Icon} from 'terraso-mobile-client/components/icons/Icon';
import {PermissionsRequestWrapper} from 'terraso-mobile-client/components/modals/PermissionsRequestWrapper';
import {
@@ -41,7 +46,8 @@ import {useNavigation} from 'terraso-mobile-client/navigation/hooks/useNavigatio
import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold';
import {SlopeMeterInfoContent} from 'terraso-mobile-client/screens/SlopeScreen/components/SlopeMeterInfoContent';
import {degreeToPercent} from 'terraso-mobile-client/screens/SlopeScreen/utils/steepnessConversion';
-import {useDispatch} from 'terraso-mobile-client/store';
+import {useDispatch, useSelector} from 'terraso-mobile-client/store';
+import {selectSite} from 'terraso-mobile-client/store/selectors';
const toDegrees = (rad: number) => Math.round(Math.abs((rad * 180) / Math.PI));
@@ -87,75 +93,88 @@ export const SlopeMeterScreen = ({siteId}: {siteId: string}) => {
navigation.pop();
}, [dispatch, siteId, deviceTiltDeg, navigation]);
+ const site = useSelector(selectSite(siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
-
-
-
- {permission?.granted ? (
-
-
-
-
-
-
-
-
- ) : (
-
- {onRequest => (
-
+
+ {() => (
+
+
+
+ {permission?.granted ? (
+
+
+
+
+
+
+
+
+ ) : (
+
+ {onRequest => (
+
+ )}
+
)}
-
- )}
-
-
-
-
-
-
-
- {t('slope.steepness.slope_meter')}
-
-
- }>
-
-
-
-
-
- {deviceTiltDeg !== null && `${deviceTiltDeg}°`}
-
-
-
- {deviceTiltDeg !== null && `${degreeToPercent(deviceTiltDeg)}%`}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {t('slope.steepness.slope_meter')}
+
+
+
+ }>
+
+
+
+
+
+ {deviceTiltDeg !== null && `${deviceTiltDeg}°`}
+
+
+
+ {deviceTiltDeg !== null &&
+ `${degreeToPercent(deviceTiltDeg)}%`}
+
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SlopeScreen/SlopeScreen.tsx b/dev-client/src/screens/SlopeScreen/SlopeScreen.tsx
index 667844a1f..6a7e9a8d5 100644
--- a/dev-client/src/screens/SlopeScreen/SlopeScreen.tsx
+++ b/dev-client/src/screens/SlopeScreen/SlopeScreen.tsx
@@ -24,6 +24,11 @@ import {InfoButton} from 'terraso-mobile-client/components/buttons/icons/common/
import {HelpContentSpacer} from 'terraso-mobile-client/components/content/HelpContentSpacer';
import {TranslatedHeading} from 'terraso-mobile-client/components/content/typography/TranslatedHeading';
import {DataInputSummary} from 'terraso-mobile-client/components/DataInputSummary';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Heading,
Row,
@@ -36,6 +41,7 @@ import {
} from 'terraso-mobile-client/screens/SlopeScreen/utils/renderValues';
import {useSelector} from 'terraso-mobile-client/store';
import {
+ selectSite,
selectSoilData,
useSiteProjectSoilSettings,
} from 'terraso-mobile-client/store/selectors';
@@ -59,32 +65,42 @@ export const SlopeScreen = ({siteId}: {siteId: string}) => {
[navigation, siteId],
);
+ const site = useSelector(selectSite(siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
-
-
- {t('slope.title')}
-
- }>
-
-
-
-
-
-
-
-
+
+ {() => (
+
+
+ {t('slope.title')}
+
+ }>
+
+
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SlopeScreen/SlopeShapeScreen.tsx b/dev-client/src/screens/SlopeScreen/SlopeShapeScreen.tsx
index c8ae57470..d03e245eb 100644
--- a/dev-client/src/screens/SlopeScreen/SlopeShapeScreen.tsx
+++ b/dev-client/src/screens/SlopeScreen/SlopeShapeScreen.tsx
@@ -39,6 +39,11 @@ import {DoneButton} from 'terraso-mobile-client/components/buttons/DoneButton';
import {InfoButton} from 'terraso-mobile-client/components/buttons/icons/common/InfoButton';
import {HelpContentSpacer} from 'terraso-mobile-client/components/content/HelpContentSpacer';
import {TranslatedHeading} from 'terraso-mobile-client/components/content/typography/TranslatedHeading';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
ImageRadio,
ImageRadioOption,
@@ -61,6 +66,7 @@ import {SlopeShapeInfoContent} from 'terraso-mobile-client/screens/SlopeScreen/c
import {renderShape} from 'terraso-mobile-client/screens/SlopeScreen/utils/renderValues';
import {useDispatch, useSelector} from 'terraso-mobile-client/store';
import {
+ selectSite,
selectSoilData,
selectUserRoleSite,
} from 'terraso-mobile-client/store/selectors';
@@ -144,40 +150,56 @@ export const SlopeShapeScreen = ({siteId}: Props) => {
[dispatch, siteId],
);
+ const site = useSelector(selectSite(siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
- } BottomNavigation={null}>
-
-
-
-
-
- {t('slope.shape.long_title')}
-
-
- }>
-
-
-
-
-
- {} : onChange}
- minimumPerRow={3}
- />
-
-
-
-
-
-
+
+ {() => (
+ }
+ BottomNavigation={null}>
+
+
+
+
+
+
+ {t('slope.shape.long_title')}
+
+
+
+ }>
+
+
+
+
+
+ {} : onChange}
+ minimumPerRow={3}
+ />
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SlopeScreen/SlopeSteepnessScreen.tsx b/dev-client/src/screens/SlopeScreen/SlopeSteepnessScreen.tsx
index d26fc9f9b..e9a6bbb56 100644
--- a/dev-client/src/screens/SlopeScreen/SlopeSteepnessScreen.tsx
+++ b/dev-client/src/screens/SlopeScreen/SlopeSteepnessScreen.tsx
@@ -24,6 +24,11 @@ import {Button, ScrollView} from 'native-base';
import {SoilIdSoilDataSlopeSteepnessSelectChoices} from 'terraso-client-shared/graphqlSchema/graphql';
import {DoneButton} from 'terraso-mobile-client/components/buttons/DoneButton';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Icon} from 'terraso-mobile-client/components/icons/Icon';
import {
ImageRadio,
@@ -59,6 +64,7 @@ import {
import {STEEPNESS_IMAGES} from 'terraso-mobile-client/screens/SlopeScreen/utils/steepnessImages';
import {useDispatch, useSelector} from 'terraso-mobile-client/store';
import {
+ selectSite,
selectSoilData,
selectUserRoleSite,
} from 'terraso-mobile-client/store/selectors';
@@ -134,66 +140,82 @@ export const SlopeSteepnessScreen = ({siteId}: Props) => {
[navigation, siteId],
);
+ const site = useSelector(selectSite(siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
- } BottomNavigation={null}>
-
-
-
-
- {t('slope.steepness.long_title')}
-
-
-
-
- {t('slope.steepness.description')}
-
-
- (
+
+ {() => (
+ }
+ BottomNavigation={null}>
+
+
+
+
+
+ {t('slope.steepness.long_title')}
+
+
+
+
+
+
+ {t('slope.steepness.description')}
+
+
+
+ (
+ }>
+ {t('slope.steepness.manual_label')}
+
+ )}>
+
+
}>
- {t('slope.steepness.manual_label')}
+ rightIcon={}
+ onPress={onMeter}>
+ {t('slope.steepness.slope_meter')}
- )}>
-
-
- }
- onPress={onMeter}>
- {t('slope.steepness.slope_meter')}
-
-
-
+
+
+
+
+ {renderSteepness(t, soilData)}
+
+
+
+ {} : onSteepnessOptionSelected}
+ minimumPerRow={2}
+ />
+
+
+
-
- {renderSteepness(t, soilData)}
-
-
-
- {} : onSteepnessOptionSelected}
- minimumPerRow={2}
- />
-
-
-
-
-
-
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SoilScreen/ColorScreen/ColorScreen.tsx b/dev-client/src/screens/SoilScreen/ColorScreen/ColorScreen.tsx
index 23208b4b4..0d97f0a25 100644
--- a/dev-client/src/screens/SoilScreen/ColorScreen/ColorScreen.tsx
+++ b/dev-client/src/screens/SoilScreen/ColorScreen/ColorScreen.tsx
@@ -23,6 +23,11 @@ import {DoneButton} from 'terraso-mobile-client/components/buttons/DoneButton';
import {InfoButton} from 'terraso-mobile-client/components/buttons/icons/common/InfoButton';
import {HelpContentSpacer} from 'terraso-mobile-client/components/content/HelpContentSpacer';
import {TranslatedHeading} from 'terraso-mobile-client/components/content/typography/TranslatedHeading';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {
Box,
Column,
@@ -52,6 +57,7 @@ import {
import {useDispatch, useSelector} from 'terraso-mobile-client/store';
import {
selectDepthDependentData,
+ selectSite,
selectUserRoleSite,
} from 'terraso-mobile-client/store/selectors';
@@ -97,57 +103,78 @@ export const ColorScreen = (props: SoilPitInputScreenProps) => {
[data],
);
+ const site = useSelector(selectSite(props.siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ // TODO-cknipe: Require the depth interval to exist for the site/project
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
-
-
-
-
-
- {t('soil.color.title')}
-
- }>
- {t('soil.color.info.p1')}
- (
-
- {t(`soil.color.info.bullet${i}`)}
-
+
+ {() => (
+
+
+
+
+
+ {t('soil.color.title')}
+
+
+ }>
+
+ {t('soil.color.info.p1')}
+
+ (
+
+ {t(`soil.color.info.bullet${i}`)}
+
+ )}
+ />
+
+ {t('soil.color.info.p2')}
+
+
+ {t('soil.color.info.p3')}
+
+
+
+
+
+ {(workflow === 'CAMERA' || color) && (
+
)}
+
+
+
+ {workflow === 'MANUAL' && }
+ {workflow === 'CAMERA' && !color && }
+ {color && (
+ <>
+
- {t('soil.color.info.p2')}
- {t('soil.color.info.p3')}
-
-
-
+ {workflow === 'CAMERA' && }
+ >
+ )}
- {(workflow === 'CAMERA' || color) && (
-
- )}
+
+
+
-
-
- {workflow === 'MANUAL' && }
- {workflow === 'CAMERA' && !color && }
- {color && (
- <>
-
- {workflow === 'CAMERA' && }
- >
- )}
-
-
-
-
-
-
-
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SoilScreen/SoilScreen.tsx b/dev-client/src/screens/SoilScreen/SoilScreen.tsx
index dc97cd9f7..c9a98d221 100644
--- a/dev-client/src/screens/SoilScreen/SoilScreen.tsx
+++ b/dev-client/src/screens/SoilScreen/SoilScreen.tsx
@@ -25,6 +25,11 @@ import {SoilIdSoilDataDepthIntervalPresetChoices} from 'terraso-client-shared/gr
import {AddDepthModalBody} from 'terraso-mobile-client/components/AddDepthModal';
import {TextButton} from 'terraso-mobile-client/components/buttons/TextButton';
import {TranslatedHeading} from 'terraso-mobile-client/components/content/typography/TranslatedHeading';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Icon} from 'terraso-mobile-client/components/icons/Icon';
import {Modal} from 'terraso-mobile-client/components/modals/Modal';
import {
@@ -47,6 +52,7 @@ import {SoilDepthSummary} from 'terraso-mobile-client/screens/SoilScreen/compone
import {SoilSurfaceStatus} from 'terraso-mobile-client/screens/SoilScreen/components/SoilSurfaceStatus';
import {useDispatch, useSelector} from 'terraso-mobile-client/store';
import {
+ selectSite,
selectSoilData,
useSiteProjectSoilSettings,
useSiteSoilIntervals,
@@ -83,71 +89,85 @@ export const SoilScreen = ({siteId}: {siteId: string}) => {
[dispatch, siteId],
);
+ const site = useSelector(selectSite(siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
-
-
-
-
- {t('soil.pit')}
- {!projectSettings && (
- }
- trigger={onOpen => (
-
- )}>
-
+ {() => (
+
+
+
+
+ {t('soil.pit')}
+ {!projectSettings && (
+
+ }
+ trigger={onOpen => (
+
+ )}>
+
+
+ )}
+
+ {allDepths.map(interval => (
+
-
- )}
-
- {allDepths.map(interval => (
-
- ))}
-
- (
- }
- onPress={onOpen}>
- {t('soil.add_depth_label')}
-
- )}
- Header={{t('soil.depth.add_title')}}>
-
-
-
-
+ ))}
+
+ (
+ }
+ onPress={onOpen}>
+ {t('soil.add_depth_label')}
+
+ )}
+ Header={
+ {t('soil.depth.add_title')}
+ }>
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/screens/SoilScreen/TextureScreen.tsx b/dev-client/src/screens/SoilScreen/TextureScreen.tsx
index bba451d53..a08a81aa7 100644
--- a/dev-client/src/screens/SoilScreen/TextureScreen.tsx
+++ b/dev-client/src/screens/SoilScreen/TextureScreen.tsx
@@ -27,8 +27,11 @@ import {DoneButton} from 'terraso-mobile-client/components/buttons/DoneButton';
import {InfoButton} from 'terraso-mobile-client/components/buttons/icons/common/InfoButton';
import {HelpContentSpacer} from 'terraso-mobile-client/components/content/HelpContentSpacer';
import {TranslatedHeading} from 'terraso-mobile-client/components/content/typography/TranslatedHeading';
-import {useHandleMissingSite} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
-import {RestrictByRequirements} from 'terraso-mobile-client/components/dataRequirements/RestrictByRequirements';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Icon} from 'terraso-mobile-client/components/icons/Icon';
import {
ImageRadio,
@@ -91,10 +94,6 @@ export const TextureScreen = (props: SoilPitInputScreenProps) => {
const isViewer = useMemo(() => isProjectViewer(userRole), [userRole]);
- const site = useSelector(state => selectSite(siteId)(state));
- const handleMissingSite = useHandleMissingSite();
- const requirements = [{data: site, doIfMissing: handleMissingSite}];
-
const onTextureChange = useCallback(
(texture: SoilTexture | null) => {
dispatch(
@@ -161,8 +160,14 @@ export const TextureScreen = (props: SoilPitInputScreenProps) => {
[dispatch, siteId, depthInterval],
);
+ const site = useSelector(selectSite(siteId));
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
-
+
{() => (
@@ -230,6 +235,6 @@ export const TextureScreen = (props: SoilPitInputScreenProps) => {
)}
-
+
);
};
diff --git a/dev-client/src/screens/SoilScreen/components/SoilSurfaceScreen.tsx b/dev-client/src/screens/SoilScreen/components/SoilSurfaceScreen.tsx
index a1c3f772b..c318e8827 100644
--- a/dev-client/src/screens/SoilScreen/components/SoilSurfaceScreen.tsx
+++ b/dev-client/src/screens/SoilScreen/components/SoilSurfaceScreen.tsx
@@ -24,6 +24,11 @@ import {
} from 'terraso-client-shared/soilId/soilIdTypes';
import {DoneButton} from 'terraso-mobile-client/components/buttons/DoneButton';
+import {useNavToBottomTabsAndShowSyncError} from 'terraso-mobile-client/components/dataRequirements/handleMissingData';
+import {
+ ScreenDataRequirements,
+ useMemoizedRequirements,
+} from 'terraso-mobile-client/components/dataRequirements/ScreenDataRequirements';
import {Select} from 'terraso-mobile-client/components/inputs/Select';
import {
Box,
@@ -70,67 +75,76 @@ export const SoilSurfaceScreen = ({siteId}: Props) => {
const isViewer = useMemo(() => isProjectViewer(userRole), [userRole]);
+ const handleMissingSite = useNavToBottomTabsAndShowSyncError();
+ const requirements = useMemoizedRequirements([
+ {data: site, doIfMissing: handleMissingSite},
+ ]);
+
return (
- }>
-
-
-
- {t('soil.collection_method.verticalCracking')}
-
-
-
+
+ {() => (
+ }>
+
+
+
+ {t('soil.collection_method.verticalCracking')}
+
+
+
- {t('soil.vertical_cracking.description')}
+ {t('soil.vertical_cracking.description')}
-
- ,
- }}
- />
-
+
+ ,
+ }}
+ />
+
-
- ,
- }}
- />
-
+
+ ,
+ }}
+ />
+
-
- ,
- }}
- />
-
+
+ ,
+ }}
+ />
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/dev-client/src/store/selectors.ts b/dev-client/src/store/selectors.ts
index 2c478e002..9cb0d6a78 100644
--- a/dev-client/src/store/selectors.ts
+++ b/dev-client/src/store/selectors.ts
@@ -77,12 +77,12 @@ export const selectProjectMembershipsWithUsers = createSelector(
export const selectProject = (projectId: string) => (state: AppState) =>
state.project.projects[projectId];
-const selectProjects = (state: AppState) => state.project.projects;
+export const selectProjects = (state: AppState) => state.project.projects;
export const selectSite = (siteId: string) => (state: AppState) =>
state.site.sites[siteId];
-const selectSites = (state: AppState) => state.site.sites;
+export const selectSites = (state: AppState) => state.site.sites;
const selectUserRole = (_state: AppState, userRole: ProjectRole) => userRole;