Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ui/feat/edit-oauth-apps #216

Merged
49 changes: 49 additions & 0 deletions ui/admin/app/components/composed/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ComponentProps, ReactNode } from "react";

import { Button } from "~/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";

export function ConfirmationDialog({
children,
title,
description,
onConfirm,
onCancel,
confirmProps,
...dialogProps
}: ComponentProps<typeof Dialog> & {
children?: ReactNode;
title: ReactNode;
description?: ReactNode;
onConfirm: () => void;
onCancel?: () => void;
confirmProps?: Omit<Partial<ComponentProps<typeof Button>>, "onClick">;
}) {
return (
<Dialog {...dialogProps}>
{children && <DialogTrigger asChild>{children}</DialogTrigger>}

<DialogContent>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
<DialogFooter>
<DialogClose onClick={onCancel} asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>

<Button {...confirmProps} onClick={onConfirm}>
{confirmProps?.children ?? "Confirm"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
8 changes: 2 additions & 6 deletions ui/admin/app/components/header/HeaderNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function getHeaderContent(route: string) {
}

if (new RegExp($path("/agents")).test(route)) {
return <AgentsContent />;
return <>Agents</>;
}

if (new RegExp($path("/threads")).test(route)) {
Expand All @@ -93,14 +93,10 @@ function getHeaderContent(route: string) {
}

if (new RegExp($path("/users")).test(route)) {
return <UsersContent />;
return <>Users</>;
}
}

const UsersContent = () => <>Users</>;

const AgentsContent = () => <>Agents</>;

const AgentEditContent = () => {
const params = useParams();
const { agent: agentId } = $params("/agents/:agent", params);
Expand Down
30 changes: 12 additions & 18 deletions ui/admin/app/components/oauth-apps/CreateOauthApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { PlusIcon } from "lucide-react";
import { useState } from "react";
import { mutate } from "swr";

import { OAuthAppSpec, OAuthAppType } from "~/lib/model/oauthApps";
import { OauthAppService } from "~/lib/service/api/oauthAppService";

import { Button } from "~/components/ui/button";
Expand All @@ -20,21 +19,17 @@ import {
PopoverContent,
PopoverTrigger,
} from "~/components/ui/popover";
import { useOAuthAppSpec } from "~/hooks/oauthApps/useOAuthAppSpec";
import { useAsync } from "~/hooks/useAsync";
import { useDisclosure } from "~/hooks/useDisclosure";

import { OAuthAppForm } from "./OAuthAppForm";

type CreateOauthAppProps = {
spec: OAuthAppSpec;
};

export function CreateOauthApp({ spec }: CreateOauthAppProps) {
export function CreateOauthApp() {
const selectModal = useDisclosure();
const { data: spec } = useOAuthAppSpec();

const [selectedAppKey, setSelectedAppKey] = useState<OAuthAppType | null>(
null
);
const [selectedAppKey, setSelectedAppKey] = useState<string | null>(null);

const createApp = useAsync(OauthAppService.createOauthApp, {
onSuccess: () => {
Expand All @@ -43,6 +38,8 @@ export function CreateOauthApp({ spec }: CreateOauthAppProps) {
},
});

const selectedSpec = selectedAppKey ? spec.get(selectedAppKey) : null;

return (
<>
<Popover
Expand All @@ -62,15 +59,13 @@ export function CreateOauthApp({ spec }: CreateOauthAppProps) {

<CommandList>
<CommandGroup>
{Object.entries(spec).map(
{Array.from(spec.entries()).map(
([key, { displayName }]) => (
<CommandItem
key={key}
value={displayName}
onSelect={() => {
setSelectedAppKey(
key as OAuthAppType
);
setSelectedAppKey(key);
selectModal.onClose();
}}
>
Expand All @@ -89,20 +84,19 @@ export function CreateOauthApp({ spec }: CreateOauthAppProps) {
onOpenChange={() => setSelectedAppKey(null)}
>
<DialogContent>
{selectedAppKey && spec[selectedAppKey] && (
{selectedAppKey && selectedSpec && (
<>
<DialogTitle>
Create {spec[selectedAppKey].displayName} OAuth
App
Create {selectedSpec.displayName} OAuth App
</DialogTitle>

<DialogDescription>
Create a new OAuth app for{" "}
{spec[selectedAppKey].displayName}
{selectedSpec.displayName}
</DialogDescription>

<OAuthAppForm
appSpec={spec[selectedAppKey]}
appSpec={selectedSpec}
onSubmit={(data) =>
createApp.execute({
type: selectedAppKey,
Expand Down
99 changes: 31 additions & 68 deletions ui/admin/app/components/oauth-apps/DeleteOAuthApp.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Check, TrashIcon, XIcon } from "lucide-react";
import { TrashIcon } from "lucide-react";
import { mutate } from "swr";

import { OauthAppService } from "~/lib/service/api/oauthAppService";

import { ConfirmationDialog } from "~/components/composed/ConfirmationDialog";
import { LoadingSpinner } from "~/components/ui/LoadingSpinner";
import { Button } from "~/components/ui/button";
import {
Expand All @@ -12,81 +13,43 @@ import {
TooltipTrigger,
} from "~/components/ui/tooltip";
import { useAsync } from "~/hooks/useAsync";
import { useDisclosure } from "~/hooks/useDisclosure";

export function DeleteOAuthApp({ id }: { id: string }) {
const confirmation = useDisclosure();

const deleteOAuthApp = useAsync(OauthAppService.deleteOauthApp, {
onSuccess: () => mutate(OauthAppService.getOauthApps.key()),
});

return (
<div className="flex gap-2">
{confirmation.isOpen ? (
<>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
onClick={confirmation.onClose}
>
<XIcon />
</Button>
</TooltipTrigger>

<TooltipContent>Cancel</TooltipContent>
</Tooltip>
</TooltipProvider>

<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="destructive"
size="icon"
disabled={deleteOAuthApp.isLoading}
onClick={() => {
deleteOAuthApp.execute(id);
confirmation.onClose();
}}
>
<Check />
</Button>
</TooltipTrigger>

<TooltipContent>Confirm Delete</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
) : (
<>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="destructive"
size="icon"
disabled={deleteOAuthApp.isLoading}
onClick={confirmation.onOpen}
>
{deleteOAuthApp.isLoading ? (
<LoadingSpinner />
) : (
<TrashIcon />
)}
</Button>
</TooltipTrigger>

<TooltipContent>Delete</TooltipContent>
</Tooltip>
</TooltipProvider>

<Button className="invisible" size="icon" />
</>
)}
<TooltipProvider>
<Tooltip>
<ConfirmationDialog
title={`Delete OAuth App`}
description="Are you sure you want to delete this OAuth app?"
onConfirm={() => deleteOAuthApp.execute(id)}
confirmProps={{
variant: "destructive",
children: "Delete",
}}
>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
disabled={deleteOAuthApp.isLoading}
>
{deleteOAuthApp.isLoading ? (
<LoadingSpinner />
) : (
<TrashIcon />
)}
</Button>
</TooltipTrigger>
</ConfirmationDialog>

<TooltipContent>Delete</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
}
68 changes: 68 additions & 0 deletions ui/admin/app/components/oauth-apps/EditOAuthApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { SquarePenIcon } from "lucide-react";
import { mutate } from "swr";

import { OAuthApp } from "~/lib/model/oauthApps";
import { OauthAppService } from "~/lib/service/api/oauthAppService";

import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { useOAuthAppSpec } from "~/hooks/oauthApps/useOAuthAppSpec";
import { useAsync } from "~/hooks/useAsync";
import { useDisclosure } from "~/hooks/useDisclosure";

import { OAuthAppForm } from "./OAuthAppForm";

type EditOAuthAppProps = {
oauthApp: OAuthApp;
};

export function EditOAuthApp({ oauthApp }: EditOAuthAppProps) {
const updateApp = useAsync(OauthAppService.updateOauthApp, {
onSuccess: async () => {
await mutate(OauthAppService.getOauthApps.key());
modal.onClose();
},
});

const modal = useDisclosure();

const { data: spec } = useOAuthAppSpec();

const typeSpec = spec.get(oauthApp.type);
if (!typeSpec) return null;

return (
<Dialog open={modal.isOpen} onOpenChange={modal.onOpenChange}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon">
<SquarePenIcon />
</Button>
</DialogTrigger>

<DialogContent>
<DialogTitle>Edit OAuth App ({oauthApp.type})</DialogTitle>

<DialogDescription hidden>
Update the OAuth app settings.
</DialogDescription>

<OAuthAppForm
appSpec={typeSpec}
oauthApp={oauthApp}
onSubmit={(data) =>
updateApp.execute(oauthApp.id, {
type: oauthApp.type,
...data,
})
}
/>
</DialogContent>
</Dialog>
);
}
Loading