From 34b1cb46bb2f666af3f7bd172be060f185796459 Mon Sep 17 00:00:00 2001 From: sepeterson <10458078+sepeterson@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:15:51 -0500 Subject: [PATCH 01/21] MOB-1346: state updates --- src/api/types.d.ts | 1 + .../Explore/ExploreV2/helpers/buildQueryParams.ts | 10 ++++------ .../Explore/ExploreV2/helpers/filtersToApiParams.ts | 6 ++++++ src/i18n/l10n/en.ftl | 2 ++ src/i18n/l10n/en.ftl.json | 1 + 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/api/types.d.ts b/src/api/types.d.ts index 77404e9e5c..84e3145001 100644 --- a/src/api/types.d.ts +++ b/src/api/types.d.ts @@ -297,5 +297,6 @@ export interface ApiObservationsSearchParams extends ApiParams { typeof ORDER_BY_UPDATED_AT | typeof ORDER_BY_VOTES; return_bounds?: boolean; + not_user_id?: number; unobserved_by_user_id?: number; } diff --git a/src/components/Explore/ExploreV2/helpers/buildQueryParams.ts b/src/components/Explore/ExploreV2/helpers/buildQueryParams.ts index 146044532f..0c9ee88e02 100644 --- a/src/components/Explore/ExploreV2/helpers/buildQueryParams.ts +++ b/src/components/Explore/ExploreV2/helpers/buildQueryParams.ts @@ -15,10 +15,8 @@ export interface ExploreV2QueryParams extends FilterApiParams { order_by: ObservationOrderBy; order: SortDirection; taxon_id?: number; - user_id?: number; - project_id?: number; - unobserved_by_user_id?: number; iconic_taxa?: string[]; + unobserved_by_user_id?: number; lat?: number; lng?: number; radius?: number; @@ -95,9 +93,9 @@ const buildExploreV2QueryParams = ( ...filterParams, }; - // `verifiable: true` excludes casual-grade observations, so drop it when the - // user explicitly asked for casual (mirrors legacy mapParamsToAPI). - if ( paramsWithFilters.quality_grade?.includes( "casual" ) ) { + // `verifiable: true` excludes casual-grade observations + // so drop it when the user explicitly asked for captive/casual + if ( paramsWithFilters.quality_grade?.includes( "casual" ) || paramsWithFilters.captive ) { delete paramsWithFilters.verifiable; } diff --git a/src/components/Explore/ExploreV2/helpers/filtersToApiParams.ts b/src/components/Explore/ExploreV2/helpers/filtersToApiParams.ts index 4b1c072a4a..90d01ec1af 100644 --- a/src/components/Explore/ExploreV2/helpers/filtersToApiParams.ts +++ b/src/components/Explore/ExploreV2/helpers/filtersToApiParams.ts @@ -30,6 +30,7 @@ export interface FilterApiParams { photo_license?: string; iconic_taxa?: string[]; user_id?: number; + not_user_id?: number; project_id?: number; } @@ -108,6 +109,11 @@ const filtersToApiParams = ( } if ( filters.user?.id ) { params.user_id = filters.user.id; } + + if ( filters.excludeUser?.id ) { + params.not_user_id = filters.excludeUser.id; + } + if ( filters.project?.id ) { params.project_id = filters.project.id; } return params; diff --git a/src/i18n/l10n/en.ftl b/src/i18n/l10n/en.ftl index 81c22dc55f..b4c3fb977a 100644 --- a/src/i18n/l10n/en.ftl +++ b/src/i18n/l10n/en.ftl @@ -79,6 +79,8 @@ Adds-your-vote-of-agreement = Adds your vote of agreement Adds-your-vote-of-disagreement = Adds your vote of disagreement # Label for toggling app mode between default and advanced settings Advanced-Mode = Advanced Mode +# Header of the advanced search screen +ADVANCED-SEARCH = ADVANCED SEARCH # Link to the advanced search screen with more search filters Advanced-Search = Advanced Search ADVANCED-SETTINGS = ADVANCED SETTINGS diff --git a/src/i18n/l10n/en.ftl.json b/src/i18n/l10n/en.ftl.json index 9e2409b795..5d602d63bc 100644 --- a/src/i18n/l10n/en.ftl.json +++ b/src/i18n/l10n/en.ftl.json @@ -33,6 +33,7 @@ "Adds-your-vote-of-agreement": "Adds your vote of agreement", "Adds-your-vote-of-disagreement": "Adds your vote of disagreement", "Advanced-Mode": "Advanced Mode", + "ADVANCED-SEARCH": "ADVANCED SEARCH", "Advanced-Search": "Advanced Search", "ADVANCED-SETTINGS": "ADVANCED SETTINGS", "Affiliation": "Affiliation: { $site }", From 31081e2671f95ff1e049e646cb0ef4ce509d6a8d Mon Sep 17 00:00:00 2001 From: sepeterson <10458078+sepeterson@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:20:56 -0500 Subject: [PATCH 02/21] MOB-1346: no permissions request in ExploreV2 location search --- .../Explore/Modals/ExploreLocationSearchModal.tsx | 4 ++-- .../Explore/SearchScreens/ExploreLocationSearch.tsx | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/Explore/Modals/ExploreLocationSearchModal.tsx b/src/components/Explore/Modals/ExploreLocationSearchModal.tsx index 0bcfa20852..0802919faf 100644 --- a/src/components/Explore/Modals/ExploreLocationSearchModal.tsx +++ b/src/components/Explore/Modals/ExploreLocationSearchModal.tsx @@ -8,8 +8,8 @@ interface Props { closeModal: () => void; hasPermissions?: boolean; onSelectNearby: ( ) => Promise | void; - renderPermissionsGate: ( options: LocationPermissionCallbacks ) => React.FC; - requestPermissions: ( ) => void; + renderPermissionsGate?: ( options: LocationPermissionCallbacks ) => React.FC; + requestPermissions?: ( ) => void; showModal: boolean; updateLocation: ( location: "worldwide" | ApiPlace ) => void; } diff --git a/src/components/Explore/SearchScreens/ExploreLocationSearch.tsx b/src/components/Explore/SearchScreens/ExploreLocationSearch.tsx index ee4ccadc74..96568c3f9c 100644 --- a/src/components/Explore/SearchScreens/ExploreLocationSearch.tsx +++ b/src/components/Explore/SearchScreens/ExploreLocationSearch.tsx @@ -32,8 +32,9 @@ interface Props { closeModal: () => void; hasPermissions?: boolean; onSelectNearby: ( ) => Promise | void; - renderPermissionsGate: RenderLocationPermissionsGateFunction; - requestPermissions: ( ) => void; + // Optional: exploreV2 relies on ExploreResults for all perms handling + renderPermissionsGate?: RenderLocationPermissionsGateFunction; + requestPermissions?: ( ) => void; updateLocation: ( location: "worldwide" | ApiPlace ) => void; } @@ -114,7 +115,7 @@ const ExploreLocationSearch = ( { }, [onSelectNearby, closeModal] ); const onNearbyPressed = () => { - if ( hasPermissions ) { + if ( hasPermissions || !requestPermissions ) { setNearbyLocation( ); } else { requestPermissions( ); @@ -173,7 +174,7 @@ const ExploreLocationSearch = ( { ListEmptyComponent={emptyListComponent} ListFooterComponent={Footer} /> - {renderPermissionsGate( { onPermissionGranted: setNearbyLocation } )} + {renderPermissionsGate?.( { onPermissionGranted: setNearbyLocation } )} ); }; From 0be516dbeb5f11cafd06eb1cd456383260ec20ee Mon Sep 17 00:00:00 2001 From: sepeterson <10458078+sepeterson@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:23:15 -0500 Subject: [PATCH 03/21] MOB-1346: not by me in context --- src/providers/ExploreV2Context.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/providers/ExploreV2Context.tsx b/src/providers/ExploreV2Context.tsx index e9a0df6a2e..ffcd4eea20 100644 --- a/src/providers/ExploreV2Context.tsx +++ b/src/providers/ExploreV2Context.tsx @@ -98,6 +98,7 @@ export interface ExploreV2Filters { iconic_taxa?: string[] | null; // User / project, in ExploreV2 parlance we always consider taxon to be the "subject" user?: ApiUser | null; + excludeUser?: ApiUser | null; project?: ApiProjectSummary | null; } From f763af3fcc7d5ea98cd4adf4592591430a3ee0c7 Mon Sep 17 00:00:00 2001 From: sepeterson <10458078+sepeterson@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:30:52 -0500 Subject: [PATCH 04/21] MOB-1346: advanced search subsection UI --- .../components/DateFilterSection.tsx | 161 ++++++++++++++++++ .../components/RadioGroupSection.tsx | 43 +++++ .../components/SelectedFilterRow.tsx | 46 +++++ 3 files changed, 250 insertions(+) create mode 100644 src/components/Explore/ExploreV2/components/DateFilterSection.tsx create mode 100644 src/components/Explore/ExploreV2/components/RadioGroupSection.tsx create mode 100644 src/components/Explore/ExploreV2/components/SelectedFilterRow.tsx diff --git a/src/components/Explore/ExploreV2/components/DateFilterSection.tsx b/src/components/Explore/ExploreV2/components/DateFilterSection.tsx new file mode 100644 index 0000000000..409cc282f4 --- /dev/null +++ b/src/components/Explore/ExploreV2/components/DateFilterSection.tsx @@ -0,0 +1,161 @@ +import classNames from "classnames"; +import { + Body1, + Button, + DateTimePicker, + Heading4, + INatIcon, + List2, + RadioButtonSheet, +} from "components/SharedComponents"; +import { View } from "components/styledComponents"; +import React, { useState } from "react"; +import { formatObsFieldDate } from "sharedHelpers/dateAndTime"; +import { useTranslation } from "sharedHooks"; +import colors from "styles/tailwindColors"; + +const toISODate = ( date: Date ): string => formatObsFieldDate( date ); + +type OpenSheet = "mode" | "exact" | "start" | "end" | null; + +interface ModeOption { + label: string; + labelCaps: string; + text?: string; + value: string; +} + +interface Props { + accessibilityLabel: string; + children?: React.ReactNode; + exactDate?: string | null; + exactMode: string; + headerText: string; + mode: string; + modeValues: Record; + onChangeExactDate: ( isoDate: string ) => void; + onChangeMode: ( mode: string ) => void; + onChangeRangeEnd: ( isoDate: string ) => void; + onChangeRangeStart: ( isoDate: string ) => void; + rangeEnd?: string | null; + rangeEndBeforeStart?: boolean; + rangeMode: string; + rangeStart?: string | null; +} + +const DateFilterSection = ( { + accessibilityLabel, + children, + exactDate, + exactMode, + headerText, + mode, + modeValues, + onChangeExactDate, + onChangeMode, + onChangeRangeEnd, + onChangeRangeStart, + rangeEnd, + rangeEndBeforeStart, + rangeMode, + rangeStart, +}: Props ) => { + const { t } = useTranslation( ); + const [openSheet, setOpenSheet] = useState( null ); + const closeSheet = ( ) => setOpenSheet( null ); + + return ( + + {headerText} +