mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-31 15:06:42 +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 AccessTokenSection from "@app/components/AccessTokenUsage";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { toUnicode } from "punycode";
|
import { toUnicode } from "punycode";
|
||||||
|
import { ResourceSelector, type SelectedResource } from "./resource-selector";
|
||||||
|
|
||||||
type FormProps = {
|
type FormProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -99,18 +100,21 @@ export default function CreateShareLinkForm({
|
|||||||
orgQueries.resources({ orgId: org?.org.orgId ?? "" })
|
orgQueries.resources({ orgId: org?.org.orgId ?? "" })
|
||||||
);
|
);
|
||||||
|
|
||||||
const resources = useMemo(
|
const [selectedResource, setSelectedResource] =
|
||||||
() =>
|
useState<SelectedResource | null>(null);
|
||||||
allResources
|
|
||||||
.filter((r) => r.http)
|
// const resources = useMemo(
|
||||||
.map((r) => ({
|
// () =>
|
||||||
resourceId: r.resourceId,
|
// allResources
|
||||||
name: r.name,
|
// .filter((r) => r.http)
|
||||||
niceId: r.niceId,
|
// .map((r) => ({
|
||||||
resourceUrl: `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/`
|
// resourceId: r.resourceId,
|
||||||
})),
|
// name: r.name,
|
||||||
[allResources]
|
// niceId: r.niceId,
|
||||||
);
|
// resourceUrl: `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/`
|
||||||
|
// })),
|
||||||
|
// [allResources]
|
||||||
|
// );
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
resourceId: z.number({ message: t("shareErrorSelectResource") }),
|
resourceId: z.number({ message: t("shareErrorSelectResource") }),
|
||||||
@@ -199,15 +203,11 @@ export default function CreateShareLinkForm({
|
|||||||
setAccessToken(token.accessToken);
|
setAccessToken(token.accessToken);
|
||||||
setAccessTokenId(token.accessTokenId);
|
setAccessTokenId(token.accessTokenId);
|
||||||
|
|
||||||
const resource = resources.find(
|
|
||||||
(r) => r.resourceId === values.resourceId
|
|
||||||
);
|
|
||||||
|
|
||||||
onCreated?.({
|
onCreated?.({
|
||||||
accessTokenId: token.accessTokenId,
|
accessTokenId: token.accessTokenId,
|
||||||
resourceId: token.resourceId,
|
resourceId: token.resourceId,
|
||||||
resourceName: values.resourceName,
|
resourceName: values.resourceName,
|
||||||
resourceNiceId: resource ? resource.niceId : "",
|
resourceNiceId: selectedResource ? selectedResource.niceId : "",
|
||||||
title: token.title,
|
title: token.title,
|
||||||
createdAt: token.createdAt,
|
createdAt: token.createdAt,
|
||||||
expiresAt: token.expiresAt
|
expiresAt: token.expiresAt
|
||||||
@@ -217,10 +217,10 @@ export default function CreateShareLinkForm({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedResourceName(id: number) {
|
// function getSelectedResourceName(id: number) {
|
||||||
const resource = resources.find((r) => r.resourceId === id);
|
// const resource = resources.find((r) => r.resourceId === id);
|
||||||
return `${resource?.name}`;
|
// return `${resource?.name}`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -241,7 +241,7 @@ export default function CreateShareLinkForm({
|
|||||||
</CredenzaDescription>
|
</CredenzaDescription>
|
||||||
</CredenzaHeader>
|
</CredenzaHeader>
|
||||||
<CredenzaBody>
|
<CredenzaBody>
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-y-4 px-1">
|
||||||
{!link && (
|
{!link && (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@@ -269,10 +269,8 @@ export default function CreateShareLinkForm({
|
|||||||
"text-muted-foreground"
|
"text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{field.value
|
{selectedResource?.name
|
||||||
? getSelectedResourceName(
|
? selectedResource.name
|
||||||
field.value
|
|
||||||
)
|
|
||||||
: t(
|
: t(
|
||||||
"resourceSelect"
|
"resourceSelect"
|
||||||
)}
|
)}
|
||||||
@@ -281,7 +279,7 @@ export default function CreateShareLinkForm({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="p-0">
|
<PopoverContent className="p-0">
|
||||||
<Command>
|
{/* <Command>
|
||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"resourceSearch"
|
"resourceSearch"
|
||||||
@@ -333,7 +331,36 @@ export default function CreateShareLinkForm({
|
|||||||
)}
|
)}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</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>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<FormMessage />
|
<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({
|
queryOptions({
|
||||||
queryKey: ["ORG", orgId, "RESOURCES"] as const,
|
queryKey: ["ORG", orgId, "RESOURCES", { query, perPage }] as const,
|
||||||
queryFn: async ({ signal, meta }) => {
|
queryFn: async ({ signal, meta }) => {
|
||||||
const sp = new URLSearchParams({
|
const sp = new URLSearchParams({
|
||||||
pageSize: "10000"
|
pageSize: perPage.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (query?.trim()) {
|
||||||
|
sp.set("query", query);
|
||||||
|
}
|
||||||
|
|
||||||
const res = await meta!.api.get<
|
const res = await meta!.api.get<
|
||||||
AxiosResponse<ListResourcesResponse>
|
AxiosResponse<ListResourcesResponse>
|
||||||
>(`/org/${orgId}/resources?${sp.toString()}`, { signal });
|
>(`/org/${orgId}/resources?${sp.toString()}`, { signal });
|
||||||
|
|||||||
Reference in New Issue
Block a user