Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend - add clear filter button #5275 #5413

Merged
merged 16 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("useNotificationSettings", () => {
service.notifications.getNotificationSettings
).toHaveBeenCalledTimes(1)
);
expect(result.current.data).toEqual(mockData);
await waitFor(() => expect(result.current.data).toEqual(mockData));
});

it("should return an error when the request fails", async () => {
Expand Down
2 changes: 1 addition & 1 deletion app/web/features/search/FilterDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ export default function FilterDialog({
className={classes.noMargin}
type="number"
variant="standard"
value={numberOfGuestFilter}
value={numberOfGuestFilter || ""}
inputProps={{ min: 0 }}
onChange={handleNumGuestsChange}
fullWidth
Expand Down
58 changes: 46 additions & 12 deletions app/web/features/search/MapWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ReplayIcon from "@mui/icons-material/Replay";
import TuneIcon from "@mui/icons-material/Tune";
import { useMediaQuery } from "@mui/material";
import Button from "components/Button";
import CenteredSpinner from "components/CenteredSpinner/CenteredSpinner";
import Map from "components/Map";
Expand Down Expand Up @@ -29,6 +30,7 @@ import {
useState,
} from "react";
import { InfiniteData } from "react-query";
import { theme } from "theme";
import { GeocodeResult, usePrevious } from "utils/hooks";
import makeStyles from "utils/makeStyles";

Expand All @@ -54,27 +56,31 @@ const useStyles = makeStyles((theme) => ({
width: "100%",
},
testChildFromGoogle: {
width: "300px",
width: "365px",
top: "30px",
display: "flex",
position: "relative",
left: "-50%",
fontSize: " 14px",
margin: "8px auto 0",
alignItems: "center",

height: "25px",
zIndex: 10,
zIndex: 1,
},
searchHereButton: {
borderRadius: "4px",
marginRight: theme.spacing(1),
},
clearFiltersButton: {
borderRadius: "4px",
marginRight: theme.spacing(1),
},
buttonSearchSettings: {
borderRadius: "0 4px 4px 0",
borderRadius: "4px",
"& span": {
margin: 0,
},
},
searchHereButton: {
borderRadius: "4px 0 0 4px",
},
}));

interface mapWrapperProps {
Expand All @@ -90,6 +96,8 @@ interface mapWrapperProps {
map: MutableRefObject<MaplibreMap | undefined>;
setWasSearchPerformed: Dispatch<SetStateAction<boolean>>;
wasSearchPerformed: boolean;
areFiltersCleared: boolean;
onClearFiltersClick: () => void;
}

export default function MapWrapper({
Expand All @@ -103,13 +111,16 @@ export default function MapWrapper({
setIsFiltersOpen,
wasSearchPerformed,
setWasSearchPerformed,
areFiltersCleared,
onClearFiltersClick,
}: mapWrapperProps) {
const { t } = useTranslation([SEARCH]);
const [areClustersLoaded, setAreClustersLoaded] = useState(false);
const [isMapStyleLoaded, setIsMapStyleLoaded] = useState(false);
const [isMapSourceLoaded, setIsMapSourceLoaded] = useState(false);
const previousResult = usePrevious(selectedResult);
const classes = useStyles();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));

/**
* User clicks on a user on map
Expand Down Expand Up @@ -229,7 +240,11 @@ export default function MapWrapper({
* Re-renders users list on map (when results array changed)
*/
useEffect(() => {
if (isMapStyleLoaded && isMapSourceLoaded && wasSearchPerformed) {
if (
isMapStyleLoaded &&
isMapSourceLoaded &&
(wasSearchPerformed || areFiltersCleared)
) {
if (results) {
const usersToRender = filterData(results);
reRenderUsersOnMap(map.current!, usersToRender, handleMapUserClick);
Expand All @@ -242,17 +257,19 @@ export default function MapWrapper({
isMapStyleLoaded,
isMapSourceLoaded,
wasSearchPerformed,
areFiltersCleared,
]);

/**
* Clicks on 'search here' button
*/
const handleOnClick = () => {
const handleSearchOnClick = () => {
const currentBbox = map.current?.getBounds().toArray();
if (currentBbox) {
if (map.current?.getBounds && locationResult) {
// Reset location but persist map position
setLocationResult({
...locationResult,
location: new LngLat(0, 0),
name: "",
simplifiedName: "",
bbox: [
Expand All @@ -267,6 +284,13 @@ export default function MapWrapper({
}
};

/**
* Clicks on 'clear filters' button
*/
const handleClearFiltersClick = () => {
onClearFiltersClick();
};

const initializeMap = (newMap: MaplibreMap) => {
map.current = newMap;
newMap.on("load", () => {
Expand All @@ -291,11 +315,21 @@ export default function MapWrapper({
<Button
color="primary"
disabled={isLoading}
onClick={handleOnClick}
onClick={handleSearchOnClick}
className={classes.searchHereButton}
endIcon={<ReplayIcon />}
>
{t("search:filter_dialog.search_here_button")}
{isMobile
? t("search:filter_dialog.mobile_search_here_button")
: t("search:filter_dialog.desktop_search_here_button")}
</Button>
<Button
color="primary"
disabled={areFiltersCleared}
onClick={handleClearFiltersClick}
className={classes.clearFiltersButton}
>
{t("search:filter_dialog.clear_filters_button")}
</Button>
<Button
color="primary"
Expand Down
7 changes: 7 additions & 0 deletions app/web/features/search/SearchPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ describe("SearchPage", () => {

await expect(screen.findByRole("alert")).rejects.toThrow();
});

it("Clear filters button should be disabled initially", () => {
render(<SearchPage locationName="" bbox={[0, 0, 0, 0]} />, { wrapper });

const clearFiltersButton = screen.getByText("Clear filters");
expect(clearFiltersButton).toBeDisabled();
});
});
58 changes: 43 additions & 15 deletions app/web/features/search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ export default function SearchPage({
const isMobile = useMediaQuery(theme.breakpoints.down("md"));

// State
const [wasSearchPerformed, setWasSearchPerformed] = useState(false);
const [wasSearchPerformed, setWasSearchPerformed] = useState(
Copy link

@nexagoai nexagoai Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[David here] maybe a boolean it's better than empty string?

Copy link

@nexagoai nexagoai Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like setting the wasSearchPerformed to false can show all the users on the map?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry if I review this late, I've been busy lately 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nexagoai locationName !== "" does return a boolean.

locationName !== ""
);
const [locationResult, setLocationResult] = useState<GeocodeResult>({
bbox: bbox,
isRegion: false,
Expand All @@ -105,6 +107,9 @@ export default function SearchPage({
>();

const [isFiltersOpen, setIsFiltersOpen] = useState(false);
const [areFiltersCleared, setAreFiltersCleared] = useState(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey David @nexagoai! If it is set to true by default then the clear filters button will be disabled on initial render, even if the map is initially rendered with a location filter (ie: someone searched Berlin from the homepage) which is an issue. That's why it's currently set to a conditional that checks whether it should be true or false

locationName === ""
);

// Loads the list of users
const { data, error, isLoading, isFetching, hasNextPage } = useInfiniteQuery<
Expand Down Expand Up @@ -148,32 +153,53 @@ export default function SearchPage({
}, [locationResult?.bbox]);

/**
* Tracks whether a search was perform after the first render (always show all the users of the platform on the first render)
* Updates filter status and controls clear filters button
*/
useEffect(() => {
if (!wasSearchPerformed) {
if (
lastActiveFilter !== 0 ||
hostingStatusFilter.length !== 0 ||
numberOfGuestFilter !== undefined ||
completeProfileFilter !== false ||
queryName !== "" ||
(locationResult.location.lng !== 0 && locationResult.location.lat !== 0)
) {
setWasSearchPerformed(true);
}
const filtersApplied =
lastActiveFilter !== 0 ||
hostingStatusFilter.length !== 0 ||
numberOfGuestFilter !== undefined ||
completeProfileFilter !== false ||
queryName !== "" ||
locationResult.name !== "" ||
wasSearchPerformed !== false;

if (!wasSearchPerformed && filtersApplied) {
setWasSearchPerformed(true);
}

setAreFiltersCleared(!filtersApplied);
}, [
lastActiveFilter,
hostingStatusFilter,
numberOfGuestFilter,
completeProfileFilter,
wasSearchPerformed,
queryName,
locationResult.location.lng,
locationResult.location.lat,
locationResult.name,
]);

/**
* Handler for clearing all filters
*/
const handleClearFilters = () => {
setQueryName("");
setLocationResult({
bbox: [390, 82, -173, -66],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these hardcoded numbers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are the hardcoded numbers that set the original map position on initial render, so upon clearing filters I set them back to the same numbers to remain consistent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh gotcha okay!

isRegion: false,
location: new LngLat(0, 0),
name: "",
simplifiedName: "",
});
setLastActiveFilter(0);
setHostingStatusFilter([]);
setNumberOfGuestFilter(undefined);
setCompleteProfileFilter(false);
setAreFiltersCleared(true);
setWasSearchPerformed(false);
};

const errorMessage = error?.message;

return (
Expand Down Expand Up @@ -247,6 +273,8 @@ export default function SearchPage({
isLoading={isLoading || isFetching}
setWasSearchPerformed={setWasSearchPerformed}
wasSearchPerformed={wasSearchPerformed}
areFiltersCleared={areFiltersCleared}
onClearFiltersClick={handleClearFilters}
/>
</div>
</div>
Expand Down
80 changes: 41 additions & 39 deletions app/web/features/search/locales/en.json
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
{
"filter_dialog": {
"search_here_button": "Search in this area",
"desktop_title": "Filters",
"mobile_title": "Search"
"filter_dialog": {
"desktop_search_here_button": "Search in this area",
"mobile_search_here_button": "Search here",
"desktop_title": "Filters",
"mobile_title": "Search",
"clear_filters_button": "Clear filters"
},
"last_active_options": {
"any": "Any",
"last_day": "Last day",
"last_week": "Last week",
"last_2_weeks": "Last 2 weeks",
"last_month": "Last month",
"last_3_months": "Last 3 months"
},
"form": {
"location_field_label": "Near location",
"missing_location_validation_error": "Specify a location to use this filter",
"keywords": {
"field_label": "Name, keywords, or username",
"clear_field_action_a11y_label": "Clear search"
},
"last_active_options": {
"any": "Any",
"last_day": "Last day",
"last_week": "Last week",
"last_2_weeks": "Last 2 weeks",
"last_month": "Last month",
"last_3_months": "Last 3 months"
"by_location_filter_label": "By location",
"by_keyword_filter_label": "By keyword or name",
"host_filters": {
"title": "Host filters",
"last_active_field_label": "Last active",
"hosting_status_field_label": "Hosting status",
"complete_profiles_label": "Complete profiles"
},
"form": {
"location_field_label": "Near location",
"missing_location_validation_error": "Specify a location to use this filter",
"keywords": {
"field_label": "Name, keywords, or username",
"clear_field_action_a11y_label": "Clear search"
},
"by_location_filter_label": "By location",
"by_keyword_filter_label": "By keyword or name",
"host_filters": {
"title": "Host filters",
"last_active_field_label": "Last active",
"hosting_status_field_label": "Hosting status",
"complete_profiles_label": "Complete profiles"
},
"accommodation_filters": {
"title": "Accommodation filters",
"guests_field_label": "Number of guests"
},
"empty_profile_filters": {
"title": "Filter out empty profiles"
},
"submit_button_label": "Apply"
"accommodation_filters": {
"title": "Accommodation filters",
"guests_field_label": "Number of guests"
},
"search_result": {
"no_user_result_message": "No users found.",
"show_user_button_label": "Show {{name}} on the map",
"missing_about_description": "{{name}} hasn't said anything about themselves yet"
}
"empty_profile_filters": {
"title": "Filter out empty profiles"
},
"submit_button_label": "Apply"
},
"search_result": {
"no_user_result_message": "No users found.",
"show_user_button_label": "Show {{name}} on the map",
"missing_about_description": "{{name}} hasn't said anything about themselves yet"
}
}