From 1438410e5a077a79fa579268610f3d733ea2b54b Mon Sep 17 00:00:00 2001 From: hiroasano Date: Fri, 6 Sep 2024 12:30:13 +0200 Subject: [PATCH] Add Confirmation Dialog and New Key Dialog --- .../[keyAuthId]/[keyId]/settings/page.tsx | 6 +- .../settings/reroll-confirmation-dialog.tsx | 45 +++++ .../[keyId]/settings/reroll-key.tsx | 164 +++++++++--------- .../settings/reroll-new-key-dialog.tsx | 73 ++++++++ 4 files changed, 204 insertions(+), 84 deletions(-) create mode 100644 apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-confirmation-dialog.tsx create mode 100644 apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-new-key-dialog.tsx diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx index 6179181508..087c96f2cf 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx @@ -2,11 +2,12 @@ import { CopyButton } from "@/components/dashboard/copy-button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Code } from "@/components/ui/code"; import { getTenantId } from "@/lib/auth"; -import { and, db, eq, isNull, Key, schema } from "@/lib/db"; +import { type Key, and, db, eq, isNull, schema } from "@/lib/db"; import { ArrowLeft } from "lucide-react"; import Link from "next/link"; import { notFound } from "next/navigation"; import { DeleteKey } from "./delete-key"; +import { RerollKey } from "./reroll-key"; import { UpdateKeyEnabled } from "./update-key-enabled"; import { UpdateKeyExpiration } from "./update-key-expiration"; import { UpdateKeyMetadata } from "./update-key-metadata"; @@ -14,7 +15,6 @@ import { UpdateKeyName } from "./update-key-name"; import { UpdateKeyOwnerId } from "./update-key-owner-id"; import { UpdateKeyRatelimit } from "./update-key-ratelimit"; import { UpdateKeyRemaining } from "./update-key-remaining"; -import { RerollKey } from "./reroll-key"; type Props = { params: { @@ -72,7 +72,7 @@ export default async function SettingsPage(props: Props) { - + ); diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-confirmation-dialog.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-confirmation-dialog.tsx new file mode 100644 index 0000000000..aa44429ddb --- /dev/null +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-confirmation-dialog.tsx @@ -0,0 +1,45 @@ +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; + +type Props = { + open: boolean; + setOpen: (open: boolean) => void; + onClick: () => void; +}; + +export function RerollConfirmationDialog({ open, setOpen, onClick }: Props) { + return ( + setOpen(o)}> + + + Reroll Key + + Make sure to replace it in your system before it expires. This action cannot be undone. + + + + + Warning + This action is not reversible. Please be certain. + + + + + + + + + ); +} diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-key.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-key.tsx index 57e687a423..57006532ed 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-key.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-key.tsx @@ -1,6 +1,5 @@ "use client"; -import { Loading } from "@/components/dashboard/loading"; import { Button } from "@/components/ui/button"; import { Card, @@ -10,12 +9,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { - Form, - FormDescription, - FormField, - FormItem, FormMessage -} from "@/components/ui/form"; +import { Form, FormDescription, FormField, FormItem, FormMessage } from "@/components/ui/form"; import { Label } from "@/components/ui/label"; import { Select, @@ -29,16 +23,17 @@ import { parseTrpcError } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import type { Key } from "@unkey/db"; import ms from "ms"; -import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { RerollConfirmationDialog } from "./reroll-confirmation-dialog"; +import { RerollNewKeyDialog } from "./reroll-new-key-dialog"; type Props = { apiId: string; apiKey: Key & { - roles: [] + roles: []; }; }; @@ -57,9 +52,6 @@ const formSchema = z.object({ }); export const RerollKey: React.FC = ({ apiKey, apiId }: Props) => { - const router = useRouter(); - const [newKeyId, setNewKeyId] = useState(); - const form = useForm>({ resolver: zodResolver(formSchema), reValidateMode: "onBlur", @@ -72,9 +64,6 @@ export const RerollKey: React.FC = ({ apiKey, apiId }: Props) => { onMutate() { toast.success("Rerolling Key"); }, - onSuccess({ keyId }) { - setNewKeyId(keyId); - }, onError(err) { console.error(err); const message = parseTrpcError(err); @@ -93,7 +82,6 @@ export const RerollKey: React.FC = ({ apiKey, apiId }: Props) => { const updateDeletedAt = trpc.key.update.deletedAt.useMutation({ onSuccess() { toast.success("Key Rerolled."); - router.push(`/apis/${apiId}/keys/${apiKey.keyAuthId}/${newKeyId}/settings`); }, onError(err) { console.error(err); @@ -103,17 +91,21 @@ export const RerollKey: React.FC = ({ apiKey, apiId }: Props) => { }); async function onSubmit(values: z.infer) { - const ratelimit = apiKey.ratelimitLimit ? { - async: apiKey.ratelimitAsync ?? false, - duration: apiKey.ratelimitDuration ?? 0, - limit: apiKey.ratelimitLimit ?? 0, - } : undefined; - - const refill = apiKey.refillInterval ? { - interval: apiKey.refillInterval ?? "daily", - amount: apiKey.refillAmount ?? 0, - } : undefined; - + const ratelimit = apiKey.ratelimitLimit + ? { + async: apiKey.ratelimitAsync ?? false, + duration: apiKey.ratelimitDuration ?? 0, + limit: apiKey.ratelimitLimit ?? 0, + } + : undefined; + + const refill = apiKey.refillInterval + ? { + interval: apiKey.refillInterval ?? "daily", + amount: apiKey.refillAmount ?? 0, + } + : undefined; + const newKey = await createKey.mutateAsync({ ...apiKey, keyAuthId: apiKey.keyAuthId, @@ -127,70 +119,80 @@ export const RerollKey: React.FC = ({ apiKey, apiId }: Props) => { refill, }); - apiKey.roles.forEach(async (role: { roleId : string }) => { - await updateNewKey.mutateAsync({ roleId: role.roleId, keyId: newKey.keyId}) + apiKey.roles?.forEach(async (role: { roleId: string }) => { + await updateNewKey.mutateAsync({ roleId: role.roleId, keyId: newKey.keyId }); }); const miliseconds = ms(values.expiresIn); const deletedAt = new Date(Date.now() + miliseconds); - + await updateDeletedAt.mutate({ keyId: apiKey.id, deletedAt, }); } + const [confirmatioDialogOpen, setConfirmationDialogOpen] = useState(false); + const confirmationSubmit = () => { + setConfirmationDialogOpen(false); + onSubmit(form.getValues()); + }; + return ( -
- - - - Reroll Key - - Rerolling creates a new identical key with the same configuration and automatically - expires the current one. Make sure to replace it in your system before it expires. - - - -
- - ( - - - - Choose an optional delay period before the old key expires. - - - - )} - /> -
-
- - - -
-
- + <> + + +
+ + + + Reroll Key + + Rerolling creates a new identical key with the same configuration and automatically + expires the current one. Make sure to replace it in your system before it expires. + + + +
+ + ( + + + + Choose an optional delay period before the old key expires. + + + + )} + /> +
+
+ + + +
+
+ + ); }; diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-new-key-dialog.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-new-key-dialog.tsx new file mode 100644 index 0000000000..3a45b42230 --- /dev/null +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/reroll-new-key-dialog.tsx @@ -0,0 +1,73 @@ +import { CopyButton } from "@/components/dashboard/copy-button"; +import { VisibleButton } from "@/components/dashboard/visible-button"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Code } from "@/components/ui/code"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { AlertCircle } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; + +type Props = { + newKey: + | { + keyId: `key_${string}`; + key: string; + } + | undefined; + apiId: string; + keyAuthId: string; +}; + +export function RerollNewKeyDialog({ newKey, apiId, keyAuthId }: Props) { + if (!newKey) { + return null; + } + + const split = newKey.key.split("_") ?? []; + const maskedKey = + split.length >= 2 + ? `${split.at(0)}_${"*".repeat(split.at(1)?.length ?? 0)}` + : "*".repeat(split.at(0)?.length ?? 0); + const [showKey, setShowKey] = useState(false); + + const [open, setOpen] = useState(Boolean(newKey)); + + return ( + setOpen(o)}> + + + Your New API Key + + + + This key is only shown once and can not be recovered + + Please pass it on to your user or store it somewhere safe. + + + +
{showKey ? newKey.key : maskedKey}
+
+ + +
+
+ + + + + + + + +
+
+ ); +}