From 0197cd5517daf3355e05fbe33fb7bd644680d3b7 Mon Sep 17 00:00:00 2001 From: Developing-Gamer Date: Wed, 27 May 2026 12:47:47 -0700 Subject: [PATCH] Add API key create and show dialogs for account settings. Co-authored-by: Cursor --- .../supporting/api-key-dialogs.tsx | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-dialogs.tsx diff --git a/apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-dialogs.tsx b/apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-dialogs.tsx new file mode 100644 index 000000000..449ec7dea --- /dev/null +++ b/apps/dashboard/src/components/dashboard-account-settings/supporting/api-key-dialogs.tsx @@ -0,0 +1,141 @@ +'use client'; + +import { yupResolver } from "@hookform/resolvers/yup"; +import { yupObject, yupString } from '@stackframe/stack-shared/dist/schema-fields'; +import { captureError } from '@stackframe/stack-shared/dist/utils/errors'; +import { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises'; +import { ActionDialog, Button, CopyField, Input, Label, Typography } from '@stackframe/stack-ui'; +import { useState } from "react"; +import { useForm } from 'react-hook-form'; +import * as yup from "yup"; +import { useUser } from "@stackframe/stack"; +import { ApiKey, ApiKeyCreationOptions, ApiKeyType } from "./types"; + +// Constants for expiration options +export const neverInMs = 1000 * 60 * 60 * 24 * 365 * 200; +export const expiresInOptions = { + [1000 * 60 * 60 * 24 * 1]: "1 day", + [1000 * 60 * 60 * 24 * 7]: "7 days", + [1000 * 60 * 60 * 24 * 30]: "30 days", + [1000 * 60 * 60 * 24 * 90]: "90 days", + [1000 * 60 * 60 * 24 * 365]: "1 year", + [neverInMs]: "Never", +} as const; + +/** + * Dialog for creating a new API key + */ +export function CreateApiKeyDialog(props: { + open: boolean, + onOpenChange: (open: boolean) => void, + onKeyCreated?: (key: ApiKey) => void, + createApiKey: (data: ApiKeyCreationOptions) => Promise>, + mockMode?: boolean, +}) { + const user = useUser({ or: props.mockMode ? 'return-null' : 'redirect' }); + const [loading, setLoading] = useState(false); + + const apiKeySchema = yupObject({ + description: yupString().defined().nonEmpty('Description is required'), + expiresIn: yupString().defined(), + }); + + const { register, handleSubmit, formState: { errors }, reset } = useForm({ + resolver: yupResolver(apiKeySchema), + defaultValues: { + description: '', + expiresIn: Object.keys(expiresInOptions)[2], // Default to 30 days + } + }); + + const onSubmit = async (data: yup.InferType) => { + setLoading(true); + try { + const expirationMs = parseInt(data.expiresIn); + const expiresAt = expirationMs === neverInMs ? undefined : new Date(Date.now() + expirationMs); + const key = await props.createApiKey({ + description: data.description, + expiresAt, + }); + props.onOpenChange(false); + reset(); + props.onKeyCreated?.(key); + } catch (e) { + captureError("api-key-create", { error: e }); + } finally { + setLoading(false); + } + }; + + return ( + { + await handleSubmit(onSubmit)(); + } + }} + > +
{ + e.preventDefault(); + runAsynchronously(handleSubmit(onSubmit)); + }}> +
+ + + {errors.description && {errors.description.message}} +
+ +
+ + +
+
+
+ ); +} + +/** + * Dialog for showing the newly created API key + */ +export function ShowApiKeyDialog(props: { + apiKey: ApiKey | null, + onClose: () => void, +}) { + return ( + props.onClose()} + title="API Key Created" + okButton={{ + label: "Close", + onClick: async () => { props.onClose(); } + }} + > +
+ + Please copy your API key now. You will not be able to see it again. + +
+ + +
+
+
+ ); +}