diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx
index f63192129..af3841818 100644
--- a/src/components/InternalResourceForm.tsx
+++ b/src/components/InternalResourceForm.tsx
@@ -60,6 +60,7 @@ import { MachinesSelector } from "./machines-selector";
import DomainPicker from "@app/components/DomainPicker";
import { SwitchInput } from "@app/components/SwitchInput";
import CertificateStatus from "@app/components/CertificateStatus";
+import { UsersSelector } from "./users-selector";
// --- Helpers (shared) ---
@@ -1522,7 +1523,7 @@ export function InternalResourceForm({
render={({ field }) => (
{t("users")}
-
+ {/*
-
+ */}
+ {
+ form.setValue(
+ "users",
+ newUsers as [
+ Tag,
+ ...Tag[]
+ ]
+ );
+ }}
+ />
)}
diff --git a/src/components/machines-selector.tsx b/src/components/machines-selector.tsx
index d4b37bb74..cfae4c2d8 100644
--- a/src/components/machines-selector.tsx
+++ b/src/components/machines-selector.tsx
@@ -5,7 +5,7 @@ import { useMemo, useState } from "react";
import { useDebounce } from "use-debounce";
import { useTranslations } from "next-intl";
-import { MultiSelectInput } from "./multi-select/multi-select-input";
+import { MultiSelectTagInput } from "./multi-select/multi-select-tag-input";
export type SelectedMachine = Pick<
ListClientsResponse["clients"][number],
@@ -46,11 +46,11 @@ export function MachinesSelector({
}
}
}
- return allMachines.slice(0, perPage);
+ return allMachines;
}, [machines, selectedMachines, debouncedValue]);
return (
- = {
ref?: Ref;
};
-export function MultiSelectTags({
+export function MultiSelectContent({
emptyPlaceholder,
searchPlaceholder,
searchQuery,
@@ -40,7 +40,8 @@ export function MultiSelectTags({
value={searchQuery}
onValueChange={onSearch}
/>
-
+ {/* FIXME: why isn't this list scrolling ????? */}
+
{emptyPlaceholder}
{options.map((option) => (
diff --git a/src/components/multi-select/multi-select-input.tsx b/src/components/multi-select/multi-select-tag-input.tsx
similarity index 95%
rename from src/components/multi-select/multi-select-input.tsx
rename to src/components/multi-select/multi-select-tag-input.tsx
index 4f4fec528..dfca035a7 100644
--- a/src/components/multi-select/multi-select-input.tsx
+++ b/src/components/multi-select/multi-select-tag-input.tsx
@@ -9,8 +9,8 @@ import { ChevronDownIcon, XIcon } from "lucide-react";
import {
type TagValue,
type MultiSelectTagsProps,
- MultiSelectTags
-} from "./multi-select-tags";
+ MultiSelectContent
+} from "./multi-select-content";
import { useState } from "react";
export interface MultiSelectInputProps<
@@ -19,7 +19,7 @@ export interface MultiSelectInputProps<
buttonText?: string;
}
-export function MultiSelectInput({
+export function MultiSelectTagInput({
buttonText,
...props
}: MultiSelectInputProps) {
@@ -83,7 +83,7 @@ export function MultiSelectInput({
-
+
);
diff --git a/src/components/roles-selector.tsx b/src/components/roles-selector.tsx
new file mode 100644
index 000000000..70b786d12
--- /dev/null
+++ b/src/components/roles-selector.tsx
@@ -0,0 +1 @@
+// TODO
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
index 8b2b6748a..67cffeec3 100644
--- a/src/components/ui/command.tsx
+++ b/src/components/ui/command.tsx
@@ -87,7 +87,7 @@ function CommandList({
void;
+};
+
+export function UsersSelector({
+ orgId,
+ selectedUsers = [],
+ onSelectUsers
+}: UsersSelectorProps) {
+ const t = useTranslations();
+ const [userSearchQuery, setUserSearchQuery] = useState("");
+
+ const [debouncedValue] = useDebounce(userSearchQuery, 150);
+
+ // TODO: switch back to 7 items
+ const perPage = 1;
+
+ const { data: users = [] } = useQuery(
+ orgQueries.users({ orgId, perPage, query: debouncedValue })
+ );
+
+ // always include the selected users in the list (if the user isn't searching)
+ const usersShown = useMemo(() => {
+ const allUsers: Array = users.map((u) => ({
+ id: u.id,
+ text: getUserDisplayName(u)
+ }));
+ if (debouncedValue.trim().length === 0) {
+ for (const user of selectedUsers) {
+ if (!allUsers.find((u) => u.id === user.id)) {
+ allUsers.unshift(user);
+ }
+ }
+ }
+ return allUsers;
+ }, [users, selectedUsers, debouncedValue]);
+
+ return (
+
+ );
+}
diff --git a/src/lib/queries.ts b/src/lib/queries.ts
index 3e38a7ba0..ab22b5b57 100644
--- a/src/lib/queries.ts
+++ b/src/lib/queries.ts
@@ -125,13 +125,29 @@ export const orgQueries = {
return res.data.data.clients;
}
}),
- users: ({ orgId }: { orgId: string }) =>
+ users: ({
+ orgId,
+ query,
+ perPage = 10_000
+ }: {
+ orgId: string;
+ query?: string;
+ perPage?: number;
+ }) =>
queryOptions({
- queryKey: ["ORG", orgId, "USERS"] as const,
+ queryKey: ["ORG", orgId, "USERS", { query, perPage }] as const,
queryFn: async ({ signal, meta }) => {
+ const sp = new URLSearchParams({
+ pageSize: perPage.toString()
+ });
+
+ if (query?.trim()) {
+ sp.set("query", query);
+ }
+
const res = await meta!.api.get<
AxiosResponse
- >(`/org/${orgId}/users`, { signal });
+ >(`/org/${orgId}/users?${sp.toString()}`, { signal });
return res.data.data.users;
}