Skip to content

Commit dfe8db1

Browse files
committed
chore(site): remove user search service
1 parent 93e4e80 commit dfe8db1

File tree

4 files changed

+83
-145
lines changed

4 files changed

+83
-145
lines changed

site/src/api/api.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,12 @@ export const getTokenConfig = async (): Promise<TypesGen.TokenConfig> => {
194194

195195
export const getUsers = async (
196196
options: TypesGen.UsersRequest,
197+
signal?: AbortSignal,
197198
): Promise<TypesGen.GetUsersResponse> => {
198199
const url = getURLWithSearchParams("/api/v2/users", options);
199-
const response = await axios.get<TypesGen.GetUsersResponse>(url.toString());
200+
const response = await axios.get<TypesGen.GetUsersResponse>(url.toString(), {
201+
signal,
202+
});
200203
return response.data;
201204
};
202205

site/src/api/queries/users.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { QueryClient } from "@tanstack/react-query";
1+
import { QueryClient, QueryOptions } from "@tanstack/react-query";
22
import * as API from "api/api";
3-
import { UpdateUserPasswordRequest, UsersRequest } from "api/typesGenerated";
3+
import {
4+
GetUsersResponse,
5+
UpdateUserPasswordRequest,
6+
UsersRequest,
7+
} from "api/typesGenerated";
48

5-
export const users = (req: UsersRequest) => {
9+
export const users = (req: UsersRequest): QueryOptions<GetUsersResponse> => {
610
return {
711
queryKey: ["users", req],
8-
queryFn: () => API.getUsers(req),
12+
queryFn: ({ signal }) => API.getUsers(req, signal),
913
};
1014
};
1115

site/src/components/UserAutocomplete/UserAutocomplete.tsx

Lines changed: 71 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,15 @@ import CircularProgress from "@mui/material/CircularProgress";
22
import { makeStyles } from "@mui/styles";
33
import TextField from "@mui/material/TextField";
44
import Autocomplete from "@mui/material/Autocomplete";
5-
import { useMachine } from "@xstate/react";
65
import { User } from "api/typesGenerated";
76
import { Avatar } from "components/Avatar/Avatar";
87
import { AvatarData } from "components/AvatarData/AvatarData";
9-
import {
10-
ChangeEvent,
11-
ComponentProps,
12-
FC,
13-
useEffect,
14-
useRef,
15-
useState,
16-
} from "react";
17-
import { searchUserMachine } from "xServices/users/searchUserXService";
8+
import { ChangeEvent, ComponentProps, FC, useState } from "react";
189
import Box from "@mui/material/Box";
1910
import { useDebouncedFunction } from "hooks/debounce";
11+
import { useQuery } from "@tanstack/react-query";
12+
import { users } from "api/queries/users";
13+
import { prepareQuery } from "utils/filters";
2014

2115
export type UserAutocompleteProps = {
2216
value: User | null;
@@ -34,53 +28,57 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
3428
size = "small",
3529
}) => {
3630
const styles = useStyles();
37-
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false);
38-
const [searchState, sendSearch] = useMachine(searchUserMachine);
39-
const { searchResults } = searchState.context;
31+
const [autoComplete, setAutoComplete] = useState<{
32+
value: string;
33+
open: boolean;
34+
}>({
35+
value: value?.email ?? "",
36+
open: false,
37+
});
38+
const usersQuery = useQuery({
39+
...users({
40+
q: prepareQuery(encodeURI(autoComplete.value)),
41+
limit: 25,
42+
}),
43+
enabled: autoComplete.open,
44+
keepPreviousData: true,
45+
});
4046

41-
// Seed list of options on the first page load if a user passes in a value.
42-
// Since some organizations have long lists of users, we do not want to load
43-
// all options on page load.
44-
const onMountRef = useRef(value);
45-
useEffect(() => {
46-
const mountValue = onMountRef.current;
47-
if (mountValue) {
48-
sendSearch("SEARCH", { query: mountValue.email });
49-
}
50-
51-
// This isn't in XState's docs, but its source code guarantees that the
52-
// memory reference of sendSearch will stay stable across renders. This
53-
// useEffect call will behave like an on-mount effect and will not ever need
54-
// to resynchronize
55-
}, [sendSearch]);
56-
57-
const { debounced: debouncedOnChange } = useDebouncedFunction(
47+
const { debounced: debouncedInputOnChange } = useDebouncedFunction(
5848
(event: ChangeEvent<HTMLInputElement>) => {
59-
sendSearch("SEARCH", { query: event.target.value });
49+
setAutoComplete((state) => ({
50+
...state,
51+
value: event.target.value,
52+
}));
6053
},
61-
1000,
54+
750,
6255
);
6356

6457
return (
6558
<Autocomplete
66-
noOptionsText="Start typing to search..."
59+
// Since the values are filtered by the API we don't need to filter them
60+
// in the FE because it can causes some mismatches.
61+
filterOptions={(user) => user}
62+
noOptionsText="No users found"
6763
className={className}
68-
options={searchResults ?? []}
69-
loading={searchState.matches("searching")}
64+
options={usersQuery.data?.users ?? []}
65+
loading={usersQuery.isLoading}
7066
value={value}
7167
id="user-autocomplete"
72-
open={isAutocompleteOpen}
68+
open={autoComplete.open}
7369
onOpen={() => {
74-
setIsAutocompleteOpen(true);
70+
setAutoComplete((state) => ({
71+
...state,
72+
open: true,
73+
}));
7574
}}
7675
onClose={() => {
77-
setIsAutocompleteOpen(false);
76+
setAutoComplete({
77+
value: value?.email ?? "",
78+
open: false,
79+
});
7880
}}
7981
onChange={(_, newValue) => {
80-
if (newValue === null) {
81-
sendSearch("CLEAR_RESULTS");
82-
}
83-
8482
onChange(newValue);
8583
}}
8684
isOptionEqualToValue={(option: User, value: User) =>
@@ -97,39 +95,37 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
9795
</Box>
9896
)}
9997
renderInput={(params) => (
100-
<>
101-
<TextField
102-
{...params}
103-
fullWidth
104-
size={size}
105-
label={label}
106-
placeholder="User email or username"
107-
className={styles.textField}
108-
InputProps={{
109-
...params.InputProps,
110-
onChange: debouncedOnChange,
111-
startAdornment: value && (
112-
<Avatar size="sm" src={value.avatar_url}>
113-
{value.username}
114-
</Avatar>
115-
),
116-
endAdornment: (
117-
<>
118-
{searchState.matches("searching") ? (
119-
<CircularProgress size={16} />
120-
) : null}
121-
{params.InputProps.endAdornment}
122-
</>
123-
),
124-
classes: {
125-
root: styles.inputRoot,
126-
},
127-
}}
128-
InputLabelProps={{
129-
shrink: true,
130-
}}
131-
/>
132-
</>
98+
<TextField
99+
{...params}
100+
fullWidth
101+
size={size}
102+
label={label}
103+
placeholder="User email or username"
104+
className={styles.textField}
105+
InputProps={{
106+
...params.InputProps,
107+
onChange: debouncedInputOnChange,
108+
startAdornment: value && (
109+
<Avatar size="sm" src={value.avatar_url}>
110+
{value.username}
111+
</Avatar>
112+
),
113+
endAdornment: (
114+
<>
115+
{usersQuery.isFetching && autoComplete.open ? (
116+
<CircularProgress size={16} />
117+
) : null}
118+
{params.InputProps.endAdornment}
119+
</>
120+
),
121+
classes: {
122+
root: styles.inputRoot,
123+
},
124+
}}
125+
InputLabelProps={{
126+
shrink: true,
127+
}}
128+
/>
133129
)}
134130
/>
135131
);

site/src/xServices/users/searchUserXService.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)