From e15703164d231c284090ee04e86b48a7e357cfe5 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 19 Mar 2026 04:44:24 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20resource=20selector=20in?= =?UTF-8?q?=20create=20share=20link=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CreateShareLinkForm.tsx | 83 +++++++++++++++++--------- src/components/resource-selector.tsx | 81 +++++++++++++++++++++++++ src/lib/queries.ts | 18 +++++- 3 files changed, 151 insertions(+), 31 deletions(-) create mode 100644 src/components/resource-selector.tsx diff --git a/src/components/CreateShareLinkForm.tsx b/src/components/CreateShareLinkForm.tsx index 2f6f9aff2..fef1fb0e1 100644 --- a/src/components/CreateShareLinkForm.tsx +++ b/src/components/CreateShareLinkForm.tsx @@ -69,6 +69,7 @@ import { import AccessTokenSection from "@app/components/AccessTokenUsage"; import { useTranslations } from "next-intl"; import { toUnicode } from "punycode"; +import { ResourceSelector, type SelectedResource } from "./resource-selector"; type FormProps = { open: boolean; @@ -99,18 +100,21 @@ export default function CreateShareLinkForm({ orgQueries.resources({ orgId: org?.org.orgId ?? "" }) ); - const resources = useMemo( - () => - allResources - .filter((r) => r.http) - .map((r) => ({ - resourceId: r.resourceId, - name: r.name, - niceId: r.niceId, - resourceUrl: `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/` - })), - [allResources] - ); + const [selectedResource, setSelectedResource] = + useState(null); + + // const resources = useMemo( + // () => + // allResources + // .filter((r) => r.http) + // .map((r) => ({ + // resourceId: r.resourceId, + // name: r.name, + // niceId: r.niceId, + // resourceUrl: `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/` + // })), + // [allResources] + // ); const formSchema = z.object({ resourceId: z.number({ message: t("shareErrorSelectResource") }), @@ -199,15 +203,11 @@ export default function CreateShareLinkForm({ setAccessToken(token.accessToken); setAccessTokenId(token.accessTokenId); - const resource = resources.find( - (r) => r.resourceId === values.resourceId - ); - onCreated?.({ accessTokenId: token.accessTokenId, resourceId: token.resourceId, resourceName: values.resourceName, - resourceNiceId: resource ? resource.niceId : "", + resourceNiceId: selectedResource ? selectedResource.niceId : "", title: token.title, createdAt: token.createdAt, expiresAt: token.expiresAt @@ -217,10 +217,10 @@ export default function CreateShareLinkForm({ setLoading(false); } - function getSelectedResourceName(id: number) { - const resource = resources.find((r) => r.resourceId === id); - return `${resource?.name}`; - } + // function getSelectedResourceName(id: number) { + // const resource = resources.find((r) => r.resourceId === id); + // return `${resource?.name}`; + // } return ( <> @@ -241,7 +241,7 @@ export default function CreateShareLinkForm({ -
+
{!link && (
- {field.value - ? getSelectedResourceName( - field.value - ) + {selectedResource?.name + ? selectedResource.name : t( "resourceSelect" )} @@ -281,7 +279,7 @@ export default function CreateShareLinkForm({ - + {/* - + */} + + { + form.setValue( + "resourceId", + r.resourceId + ); + form.setValue( + "resourceName", + r.name + ); + form.setValue( + "resourceUrl", + `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/` + ); + setSelectedResource( + r + ); + }} + /> diff --git a/src/components/resource-selector.tsx b/src/components/resource-selector.tsx new file mode 100644 index 000000000..a940894b0 --- /dev/null +++ b/src/components/resource-selector.tsx @@ -0,0 +1,81 @@ +import { orgQueries } from "@app/lib/queries"; +import { useQuery } from "@tanstack/react-query"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from "./ui/command"; +import { useState } from "react"; +import { useTranslations } from "next-intl"; +import { CheckIcon } from "lucide-react"; +import { cn } from "@app/lib/cn"; +import type { ListResourcesResponse } from "@server/routers/resource"; +import { useDebounce } from "use-debounce"; + +export type SelectedResource = Pick< + ListResourcesResponse["resources"][number], + "name" | "resourceId" | "fullDomain" | "niceId" | "ssl" +>; + +export type ResourceSelectorProps = { + orgId: string; + selectedResource?: SelectedResource | null; + onSelectResource: (resource: SelectedResource) => void; +}; + +export function ResourceSelector({ + orgId, + selectedResource, + onSelectResource +}: ResourceSelectorProps) { + const t = useTranslations(); + const [resourceSearchQuery, setResourceSearchQuery] = useState(""); + + const [debouncedSearchQuery] = useDebounce(resourceSearchQuery, 150); + + const { data: resources = [] } = useQuery( + orgQueries.resources({ + orgId: orgId, + query: debouncedSearchQuery, + perPage: 10 + }) + ); + + return ( + + + + {t("resourcesNotFound")} + + {resources.map((r) => ( + { + onSelectResource(r); + }} + > + + {`${r.name}`} + + ))} + + + + ); +} diff --git a/src/lib/queries.ts b/src/lib/queries.ts index f6de28fc0..dfa706a88 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -191,14 +191,26 @@ export const orgQueries = { } }), - resources: ({ orgId }: { orgId: string }) => + resources: ({ + orgId, + query, + perPage = 10_000 + }: { + orgId: string; + query?: string; + perPage?: number; + }) => queryOptions({ - queryKey: ["ORG", orgId, "RESOURCES"] as const, + queryKey: ["ORG", orgId, "RESOURCES", { query, perPage }] as const, queryFn: async ({ signal, meta }) => { const sp = new URLSearchParams({ - pageSize: "10000" + pageSize: perPage.toString() }); + if (query?.trim()) { + sp.set("query", query); + } + const res = await meta!.api.get< AxiosResponse >(`/org/${orgId}/resources?${sp.toString()}`, { signal });