mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-30 10:25:39 +00:00
♻️ resource selector in create share link form
This commit is contained in:
@@ -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<SelectedResource | null>(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({
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-y-4 px-1">
|
||||
{!link && (
|
||||
<Form {...form}>
|
||||
<form
|
||||
@@ -269,10 +269,8 @@ export default function CreateShareLinkForm({
|
||||
"text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value
|
||||
? getSelectedResourceName(
|
||||
field.value
|
||||
)
|
||||
{selectedResource?.name
|
||||
? selectedResource.name
|
||||
: t(
|
||||
"resourceSelect"
|
||||
)}
|
||||
@@ -281,7 +279,7 @@ export default function CreateShareLinkForm({
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0">
|
||||
<Command>
|
||||
{/* <Command>
|
||||
<CommandInput
|
||||
placeholder={t(
|
||||
"resourceSearch"
|
||||
@@ -333,7 +331,36 @@ export default function CreateShareLinkForm({
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</Command> */}
|
||||
|
||||
<ResourceSelector
|
||||
orgId={
|
||||
org.org
|
||||
.orgId
|
||||
}
|
||||
selectedResource={
|
||||
selectedResource
|
||||
}
|
||||
onSelectResource={(
|
||||
r
|
||||
) => {
|
||||
form.setValue(
|
||||
"resourceId",
|
||||
r.resourceId
|
||||
);
|
||||
form.setValue(
|
||||
"resourceName",
|
||||
r.name
|
||||
);
|
||||
form.setValue(
|
||||
"resourceUrl",
|
||||
`${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/`
|
||||
);
|
||||
setSelectedResource(
|
||||
r
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
|
||||
81
src/components/resource-selector.tsx
Normal file
81
src/components/resource-selector.tsx
Normal file
@@ -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 (
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={t("resourceSearch")}
|
||||
value={resourceSearchQuery}
|
||||
onValueChange={setResourceSearchQuery}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>{t("resourcesNotFound")}</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{resources.map((r) => (
|
||||
<CommandItem
|
||||
value={`${r.name}:${r.resourceId}`}
|
||||
key={r.resourceId}
|
||||
onSelect={() => {
|
||||
onSelectResource(r);
|
||||
}}
|
||||
>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
r.resourceId ===
|
||||
selectedResource?.resourceId
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{`${r.name}`}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
);
|
||||
}
|
||||
@@ -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<ListResourcesResponse>
|
||||
>(`/org/${orgId}/resources?${sp.toString()}`, { signal });
|
||||
|
||||
Reference in New Issue
Block a user