"use client"; import { CopyButton } from "@/components/dashboard/copy-button"; import { Loading } from "@/components/dashboard/loading"; import { VisibleButton } from "@/components/dashboard/visible-button"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Code } from "@/components/ui/code"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/components/ui/toaster"; // import { trpc } from "@/lib/trpc/client"; import { zodResolver } from "@hookform/resolvers/zod"; import { AlertCircle } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; const getDatePlusTwoMinutes = () => { const now = new Date(); const futureDate = new Date(now.getTime() + 2 * 60000); return futureDate.toISOString().slice(0, -8); }; const formSchema = z.object({ bytes: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ message: issue.code === "invalid_type" ? "Amount must be a number and greater than 0" : defaultError, }), }) .default(16), prefix: z .string() .max(8, { message: "Please limit the prefix to under 8 characters." }) .optional(), ownerId: z.string().optional(), name: z.string().optional(), metaEnabled: z.boolean().default(false), meta: z .string() .refine( (s) => { try { JSON.parse(s); return true; } catch { return false; } }, { message: "Must be valid json", }, ) .optional(), limitEnabled: z.boolean().default(false), limit: z .object({ remaining: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ message: issue.code === "invalid_type" ? "Remaining amount must be greater than 0" : defaultError, }), }) .int() .positive({ message: "Please enter a positive number" }) .optional(), refill: z .object({ interval: z.enum(["none", "daily", "monthly"]).default("none"), amount: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ message: issue.code === "invalid_type" ? "Refill amount must be greater than 0 and a integer" : defaultError, }), }) .int() .min(1) .positive() .optional(), }) .optional(), }) .optional(), expireEnabled: z.boolean().default(false), expires: z.coerce .date() .min(new Date(new Date().getTime() + 2 * 60000)) .optional(), ratelimitEnabled: z.boolean().default(false), ratelimit: z .object({ type: z.enum(["consistent", "fast"]).default("fast"), refillInterval: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ message: issue.code === "invalid_type" ? "Refill interval must be greater than 0" : defaultError, }), }) .positive({ message: "Refill interval must be greater than 0" }), refillRate: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ message: issue.code === "invalid_type" ? "Refill rate must be greater than 0" : defaultError, }), }) .positive({ message: "Refill rate must be greater than 0" }), limit: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ message: issue.code === "invalid_type" ? "Refill limit must be greater than 0" : defaultError, }), }) .positive({ message: "Limit must be greater than 0" }), }) .optional(), environment: z.string().optional(), }); type Props = { keyAuthId: string; }; export const CreateKey: React.FC = ({ keyAuthId }) => { const router = useRouter(); const form = useForm>({ resolver: async (data, context, options) => { return zodResolver(formSchema)(data, context, options); }, mode: "all", shouldFocusError: true, delayError: 100, defaultValues: { bytes: 16, expireEnabled: false, limitEnabled: false, metaEnabled: false, ratelimitEnabled: false, }, }); // const key = trpc.key.create.useMutation({ // onSuccess() { // toast("Key Created", { // description: "Your Key has been created", // }); // }, // onError(_err) { // toast.error("An error occured, please try again"); // }, // }); async function onSubmit(values: z.infer) { // make sure they aren't sent to the server if they are disabled. if (!values.expireEnabled) { delete values.expires; } if (!values.metaEnabled) { delete values.meta; } if (!values.limitEnabled) { delete values.limit; } if (!values.ratelimitEnabled) { delete values.ratelimit; } // await key.mutateAsync({ // keyAuthId, // ...values, // meta: values.meta ? JSON.parse(values.meta) : undefined, // expires: values.expires?.getTime() ?? undefined, // ownerId: values.ownerId ?? undefined, // remaining: values.limit?.remaining ?? undefined, // enabled: true, // }); } const key = { data: { key: "xsadsds" } } const snippet = `curl -XPOST '${process.env.NEXT_PUBLIC_UNKEY_API_URL ?? "https://api.unkey.dev"}/v1/keys.verifyKey' \\ -H 'Content-Type: application/json' \\ -d '{ "key": "${key.data?.key}" }'`; const split = key.data?.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 [showKeyInSnippet, setShowKeyInSnippet] = useState(false); const resetRateLimit = () => { // set them to undefined so the form resets properly. form.resetField("ratelimit.refillRate", undefined); form.resetField("ratelimit.refillInterval", undefined); form.resetField("ratelimit.limit", undefined); form.resetField("ratelimit", undefined); }; const resetLimited = () => { form.resetField("limit.refill.amount", undefined); form.resetField("limit.refill.interval", undefined); form.resetField("limit.refill", undefined); form.resetField("limit.remaining", undefined); form.resetField("limit", undefined); }; useEffect(() => { // React hook form + zod doesn't play nice with nested objects, so we need to reset them on load. resetRateLimit(); resetLimited(); }, []); return ( <> {key.data ? (

Your 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 ? key.data.key : maskedKey}

Try verifying it:

                {showKeyInSnippet ? snippet : snippet.replace(key.data.key, maskedKey)}
              
) : ( <>

Create a new key

( Prefix{" "} Optional { if (e.target.value === "") { return; } }} /> Using a prefix can make it easier for your users to distinguish between apis. Don't add a trailing underscore, we'll do that automatically. )} /> ( Bytes{" "} Optional How long the key will be. Longer keys are harder to guess and more secure. )} /> ( Owner{" "} Optional This is the id of the user or workspace in your system, so you can identify users from an API key. )} /> ( Name{" "} Optional To make it easier to identify a particular key, you can provide a name. )} /> ( Environment{" "} Optional Separate keys into different environments, for example{" "} test and live. )} />
Ratelimit ( Ratelimit { field.onChange(e); if (field.value === false) { resetRateLimit(); } }} /> )} />
{form.watch("ratelimitEnabled") ? ( <>
( Limit The maximum number of requests possible during a burst. )} /> ( Refill Rate )} /> ( Refill Interval (milliseconds) )} /> How many requests may be performed in a given interval
{form.formState.errors.ratelimit && (

{form.formState.errors.ratelimit.message}

)} ) : null}
Limited Use ( Limited Use { field.onChange(e); if (field.value === false) { resetLimited(); } }} /> )} />
{form.watch("limitEnabled") ? ( <>

How many times this key can be used before it gets disabled automatically.

( Number of uses Enter the remaining amount of uses for this key. )} /> ( Refill Rate )} /> ( Number of uses per interval Enter the number of uses to refill per interval. )} /> How many requests may be performed in a given interval
{form.formState.errors.ratelimit && (

{form.formState.errors.ratelimit.message}

)} ) : null}
Expiration ( Expiration { field.onChange(e); if (field.value === false) { resetLimited(); } }} /> )} />
{form.watch("expireEnabled") ? ( <>

{" "} Automatically revoke this key after a certain date.

( Expiry Date This api key will automatically be revoked after the given date. )} /> How many requests may be performed in a given interval
{form.formState.errors.ratelimit && (

{form.formState.errors.ratelimit.message}

)} ) : null}
Metadata ( Metadata { field.onChange(e); if (field.value === false) { resetLimited(); } }} /> )} />
{form.watch("metaEnabled") ? ( <>

Store json, or any other data you want to associate with this key. Whenever you verify this key, we'll return the metadata to you. Enter custom metadata as a JSON object.Format Json

(