From f91e788b2e556dfca7598d777e65d542a1a0b2b4 Mon Sep 17 00:00:00 2001 From: Bakeiro Date: Mon, 21 Oct 2024 10:54:47 +0200 Subject: [PATCH 1/2] slider + minor changes --- .../LocationAutocomplete.tsx | 3 ++ app/web/features/search/AgeSlider.tsx | 49 +++++++++++++++++++ app/web/features/search/locales/en.json | 29 ++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 app/web/features/search/AgeSlider.tsx diff --git a/app/web/components/LocationAutocomplete/LocationAutocomplete.tsx b/app/web/components/LocationAutocomplete/LocationAutocomplete.tsx index 109b283958..c6d196fb02 100644 --- a/app/web/components/LocationAutocomplete/LocationAutocomplete.tsx +++ b/app/web/components/LocationAutocomplete/LocationAutocomplete.tsx @@ -18,6 +18,7 @@ interface LocationAutocompleteProps { id?: string; variant?: "filled" | "standard" | "outlined" | undefined; name: string; + size?: "small" | "medium"; onChange?(value: GeocodeResult | ""): void; required?: string; showFullDisplayName?: boolean; @@ -36,6 +37,7 @@ export default function LocationAutocomplete({ variant = "standard", onChange, required, + size = "medium", showFullDisplayName = false, disableRegions = false, }: LocationAutocompleteProps) { @@ -113,6 +115,7 @@ export default function LocationAutocomplete({ : t("location_autocomplete.search_location_hint") } loading={isLoading} + size={size} options={options || []} open={isOpen} onClose={() => setIsOpen(false)} diff --git a/app/web/features/search/AgeSlider.tsx b/app/web/features/search/AgeSlider.tsx new file mode 100644 index 0000000000..ab44c6706f --- /dev/null +++ b/app/web/features/search/AgeSlider.tsx @@ -0,0 +1,49 @@ +import Slider from '@material-ui/core/Slider'; +import { withStyles } from "@material-ui/core"; + +export const AgeSlider = withStyles({ + root: { + color: '#3a8589', + height: 3, + padding: '13px 0', + }, + valueLabel: { + left: 'calc(-50% + 9px)', + top: '35px', + '& *': { + background: 'transparent', + color: '#000', + }, + fontSize: '13px' + }, + thumb: { + height: 27, + width: 27, + backgroundColor: '#fff', + border: '1px solid currentColor', + marginTop: -12, + marginLeft: -13, + boxShadow: '#ebebeb 0 2px 2px', + '&:focus, &:hover, &$active': { + boxShadow: '#ccc 0 2px 3px 1px', + }, + '& .bar': { + // display: inline-block !important; + height: 9, + width: 1, + backgroundColor: 'currentColor', + marginLeft: 1, + marginRight: 1, + }, + }, + active: {}, + track: { + height: 3, + }, + rail: { + color: '#d8d8d8', + opacity: 1, + height: 3, + }, + })(Slider); + diff --git a/app/web/features/search/locales/en.json b/app/web/features/search/locales/en.json index 06706542eb..6d39bb5b02 100644 --- a/app/web/features/search/locales/en.json +++ b/app/web/features/search/locales/en.json @@ -28,8 +28,33 @@ "complete_profiles_label": "Complete profiles" }, "accommodation_filters": { - "title": "Accommodation filters", - "guests_field_label": "Number of guests" + "title": "Type of place", + "guests_field_label": "Number of guests", + "private_room": "Private room", + "shared_room": "Shared room" + }, + "rules": { + "title": "Rules", + "smoking": "Smoking allowed", + "drinking": "Alcohol allowed", + "pets": "Pets allowed", + "kids": "Kids allowed" + }, + "hosting_status": { + "title": "Hosting status", + "accepting_guests": "Accepting guests", + "maybe_accepting_guests": "Maybe accepting guests", + "can_meetup": "Want meet up", + "open_meetup": "Open to meet up" + }, + "age": { + "title": "Age" + }, + "other": { + "title": "Other", + "camping": "Camping available", + "parking": "Parking available", + "wheelchair_accessible": "Wheelchair accessible" }, "empty_profile_filters": { "title": "Filter out empty profiles" From 306a82e416cfeca27a85b6859483721a838966da Mon Sep 17 00:00:00 2001 From: Bakeiro Date: Mon, 21 Oct 2024 10:54:56 +0200 Subject: [PATCH 2/2] prepare work --- app/web/features/search/FilterDialog.tsx | 310 ++++++++++++++++++----- app/web/features/search/SearchPage.tsx | 24 +- app/web/service/search.ts | 59 ++++- 3 files changed, 321 insertions(+), 72 deletions(-) diff --git a/app/web/features/search/FilterDialog.tsx b/app/web/features/search/FilterDialog.tsx index 1c4f1b7bf6..2a28821eb7 100644 --- a/app/web/features/search/FilterDialog.tsx +++ b/app/web/features/search/FilterDialog.tsx @@ -1,6 +1,5 @@ import { Checkbox, - Chip, FormControlLabel, Grid, Input, @@ -14,10 +13,9 @@ import Button from "components/Button"; import { Dialog, DialogActions, - DialogContent, DialogTitle, } from "components/Dialog"; -import Divider from "components/Divider"; +import { AgeSlider } from "./AgeSlider"; import IconButton from "components/IconButton"; import { CrossIcon } from "components/Icons"; import LocationAutocomplete from "components/LocationAutocomplete"; @@ -26,7 +24,7 @@ import TextField from "components/TextField"; import { TFunction, useTranslation } from "i18n"; import { GLOBAL, SEARCH } from "i18n/namespaces"; import { HostingStatus } from "proto/api_pb"; -import { Dispatch, SetStateAction } from "react"; +import { Dispatch, SetStateAction, useState } from "react"; import { useForm } from "react-hook-form"; import { GeocodeResult } from "utils/hooks"; import SearchFilters from "utils/searchFilters"; @@ -53,6 +51,7 @@ const getLastActiveOptions = (t: TFunction) => ({ ), }); +// TODO: needed? const getHostingStatusOptions = (t: TFunction) => ({ [HostingStatus.HOSTING_STATUS_CAN_HOST]: t("global:hosting_status.can_host"), [HostingStatus.HOSTING_STATUS_MAYBE]: t("global:hosting_status.maybe"), @@ -66,6 +65,16 @@ const useStyles = makeStyles((theme) => ({ "& > * + *": { marginBlockStart: theme.spacing(1), }, + marginBottom: 15 + }, + content: { + height: "fit-content", + padding: theme.spacing(3), + width: "100%", + paddingTop: 0, + }, + titleCategory: { + fontSize: "1.1rem", }, marginBottom: { marginBottom: theme.spacing(2), @@ -85,8 +94,9 @@ const useStyles = makeStyles((theme) => ({ noMargin: { margin: 0, }, - noLeftPadding: { + checkboxPadding: { paddingLeft: 0, + padding: 4, }, inputHostingStatus: { minWidth: "160px", @@ -143,22 +153,13 @@ export default function FilterDialog({ mode: "onBlur", }); - const MenuProps = { - PaperProps: { - style: { - maxHeight: 48 * 4.5 + 8, - width: 250, - }, - }, - MenuListProps: { - className: classes.smallLeftPadding, - }, - }; - const isSmDown = useMediaQuery((theme: Theme) => theme.breakpoints.down("sm") ); + // TODO: should come from parent + const [valueSlider, setValueSlider] = useState([20, 37]); + const handleNumGuestsChange = (e: React.ChangeEvent) => { const convertedValue = parseInt(e.target.value); const tempNumOfGuest = @@ -193,11 +194,12 @@ export default function FilterDialog({ onClose(); }} > - +
{ @@ -213,6 +215,7 @@ export default function FilterDialog({ id="keywords-filter" label={t("search:form.keywords.field_label")} name="query" + size="small" inputRef={register} variant="standard" onChange={(e) => { @@ -240,15 +243,15 @@ export default function FilterDialog({ }} />
- - + {t("search:form.host_filters.title")} - setHostingStatusFilter( - e.target.value as TypeHostingStatusOptions - ) - } - input={ - { + setCompleteProfileFilter(!completeProfileFilter); + }} /> } - className={classes.marginBottom} - native={false} - renderValue={(selected) => ( -
- {(selected as TypeHostingStatusOptions).map( - // Type coercion bc the selected value type is unknown in the mui Select component - ( - value: Exclude< - HostingStatus, - | HostingStatus.HOSTING_STATUS_UNKNOWN - | HostingStatus.HOSTING_STATUS_UNSPECIFIED - > - ) => ( - - ) - )} -
- )} - MenuProps={MenuProps} - optionLabelMap={getHostingStatusOptions(t)} - options={[ - HostingStatus.HOSTING_STATUS_CAN_HOST, - HostingStatus.HOSTING_STATUS_MAYBE, - HostingStatus.HOSTING_STATUS_CANT_HOST, - ]} + label={t("search:form.empty_profile_filters.title")} /> + + {t("search:form.hosting_status.title")} + + { @@ -325,17 +298,76 @@ export default function FilterDialog({ }} /> } - label={t("search:form.empty_profile_filters.title")} + label={t("search:form.hosting_status.accepting_guests")} /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.hosting_status.maybe_accepting_guests")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.hosting_status.open_meetup")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.hosting_status.can_meetup")} + /> + + + {t("search:form.age.title")} + + + +
- + {t("search:form.accommodation_filters.title")} + { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.accommodation_filters.private_room")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.accommodation_filters.shared_room")} + /> + + {t("search:form.rules.title")} + + { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.rules.drinking")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.rules.smoking")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.rules.pets")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.rules.kids")} + /> + + {t("search:form.other.title")} + + { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.other.camping")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.other.parking")} + /> +
+ { + setCompleteProfileFilter(!completeProfileFilter); + }} + /> + } + label={t("search:form.other.wheelchair_accessible")} + />
- +
diff --git a/app/web/features/search/SearchPage.tsx b/app/web/features/search/SearchPage.tsx index bd16a09df1..f9ef533192 100644 --- a/app/web/features/search/SearchPage.tsx +++ b/app/web/features/search/SearchPage.tsx @@ -4,7 +4,7 @@ import { Coordinates } from "features/search/constants"; import { useTranslation } from "i18n"; import { GLOBAL, SEARCH } from "i18n/namespaces"; import { LngLat, Map as MaplibreMap } from "maplibre-gl"; -import { HostingStatus, User } from "proto/api_pb"; +import { HostingStatus, User, SleepingArrangement } from "proto/api_pb"; import { UserSearchRes } from "proto/search_pb"; import { useEffect, useRef, useState } from "react"; import { @@ -25,6 +25,14 @@ export type TypeHostingStatusOptions = Exclude< | HostingStatus.HOSTING_STATUS_UNSPECIFIED >[]; +export type TypeSleepingStatusOptions = Exclude< + SleepingArrangement, + | SleepingArrangement.SLEEPING_ARRANGEMENT_UNSPECIFIED + | SleepingArrangement.SLEEPING_ARRANGEMENT_UNKNOWN + | SleepingArrangement.SLEEPING_ARRANGEMENT_COMMON + | SleepingArrangement.SLEEPING_ARRANGEMENT_SHARED_SPACE +>[]; + const useStyles = makeStyles((theme) => ({ container: { display: "flex", @@ -79,6 +87,20 @@ export default function SearchPage({ const map = useRef(); // State + const [acceptsKids, setAcceptKids] = useState(false); + const [acceptsPets, setAcceptPets] = useState(false); + const [drinkingAllowed, setDrinkingAllowed] = useState(false); + const [smokingAllowed, setSmokingAllowed] = useState(false); + + // Other + const [wheelChairAccessible, setWheelChairAccessible] = useState(false); + const [hasParking, setHasParking] = useState(false); + const [campingOk, setCampingOk] = useState(false); + const [sleepingArrangement, setSleepingArrangement] = useState([]); + + // Other + // TODO: make 2 toggles for the hosting status + const [wasSearchPerformed, setWasSearchPerformed] = useState(false); const [locationResult, setLocationResult] = useState({ bbox: bbox, diff --git a/app/web/service/search.ts b/app/web/service/search.ts index fa0c65ac1b..2943866f21 100644 --- a/app/web/service/search.ts +++ b/app/web/service/search.ts @@ -5,7 +5,7 @@ import { StringValue, UInt32Value, } from "google-protobuf/google/protobuf/wrappers_pb"; -import { HostingStatus } from "proto/api_pb"; +import { HostingStatus, SleepingArrangement } from "proto/api_pb"; import { RectArea, UserSearchReq } from "proto/search_pb"; import client from "service/client"; @@ -16,6 +16,14 @@ export interface UserSearchFilters { hostingStatusOptions?: HostingStatus[]; numGuests?: number; completeProfile?: boolean; + acceptsKids?: boolean; + acceptsPets?: boolean; + drinkingAllowed?: boolean; + smokingAllowed?: boolean; + wheelChairAccessible?: boolean; + hasParking?: boolean; + campingOk?: boolean; + sleepingArrangement?: SleepingArrangement[]; } export async function userSearch( @@ -26,12 +34,61 @@ export async function userSearch( hostingStatusOptions, numGuests, completeProfile, + acceptsKids, + acceptsPets, + drinkingAllowed, + smokingAllowed, + wheelChairAccessible, + hasParking, + campingOk, + sleepingArrangement, }: UserSearchFilters, pageToken = "" ) { const req = new UserSearchReq(); req.setPageToken(pageToken); + // Rules + if (acceptsKids) { + req.setAcceptsKids(new BoolValue().setValue(acceptsKids)); + } + if (acceptsPets) { + req.setAcceptsPets(new BoolValue().setValue(acceptsPets)); + } + if (drinkingAllowed) { + req.setDrinkingAllowed(new BoolValue().setValue(drinkingAllowed)); + } + if (smokingAllowed) { + // setSmokingAllowed(false); // TODO: missing in backend + } + + // Other + if (wheelChairAccessible) { + req.setWheelchairAccessible(new BoolValue().setValue(wheelChairAccessible)); + } + if (hasParking) { + req.setParking(new BoolValue().setValue(hasParking)); + } + if (campingOk) { + req.setCampingOk(new BoolValue().setValue(campingOk)); + } + + // Type of place + if (sleepingArrangement) { + req.setSleepingArrangementFilterList(sleepingArrangement); + } + /* + SLEEPING_ARRANGEMENT_PRIVATE = 2, + SLEEPING_ARRANGEMENT_SHARED_ROOM = 4 + */ + + // hosting status + req.setHostingStatusFilterList([]); + /* + HostingStatus.HOSTING_STATUS_CAN_HOST + HostingStatus.HOSTING_STATUS_MAYBE + */ + if (query) { req.setQuery(new StringValue().setValue(query)); }