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 ( - } /> - }> - - - - - - - - - - ( - - )} - 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')} - - - - - - - - + + {() => ( + } /> + }> + + + + + + + + + + ( + + )} + 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('soil.pit')} + + }> + + + + + {t('soil.project_settings.required_data_title')} + + }> + + + - {t('projects.inputs.instructions.title')} - {t('projects.inputs.instructions.description')} - + 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')} + + + + ( + + )}> + + - )}> - - - - - + + + + + {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 => ( - - ))} - - ( - - )} - Header={{t('soil.depth.add_title')}}> - - - - + ))} + + ( + + )} + 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.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;