diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/controls/identities-search.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/controls/identities-search.tsx new file mode 100644 index 0000000000..cd0299474d --- /dev/null +++ b/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/controls/identities-search.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { LLMSearch } from "@unkey/ui"; +import { parseAsString, useQueryState } from "nuqs"; + +export const IdentitiesSearch = () => { + const [_search, setSearch] = useQueryState( + "search", + parseAsString.withDefault("").withOptions({ + history: "replace", + shallow: true, + clearOnDefault: true, + }), + ); + + return ( + { + setSearch(query); + }} + /> + ); +}; diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/controls/index.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/controls/index.tsx new file mode 100644 index 0000000000..4d3e8a830c --- /dev/null +++ b/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/controls/index.tsx @@ -0,0 +1,12 @@ +import { ControlsContainer, ControlsLeft } from "@/components/logs/controls-container"; +import { IdentitiesSearch } from "./identities-search"; + +export function IdentitiesListControls() { + return ( + + + + + + ); +} diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/create-identity-dialog.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/create-identity-dialog.tsx new file mode 100644 index 0000000000..1174cd4e81 --- /dev/null +++ b/apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/create-identity-dialog.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { NavbarActionButton } from "@/components/navigation/action-button"; +import { trpc } from "@/lib/trpc/client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Plus } from "@unkey/icons"; +import { Button, DialogContainer, FormInput, toast } from "@unkey/ui"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +const formSchema = z.object({ + externalId: z + .string() + .transform((s) => s.trim()) + .refine((trimmed) => trimmed.length >= 3, "External ID must be at least 3 characters") + .refine((trimmed) => trimmed.length <= 255, "External ID must be 255 characters or fewer") + .refine((trimmed) => trimmed !== "", "External ID cannot be only whitespace"), + meta: z + .string() + .optional() + .refine( + (val) => { + if (!val || val.trim() === "") { + return true; + } + try { + JSON.parse(val); + // Check size limit (1MB) + const size = new Blob([val]).size; + return size < 1024 * 1024; + } catch { + return false; + } + }, + { + message: "Must be valid JSON and less than 1MB", + }, + ), +}); + +type FormValues = z.infer; + +export function CreateIdentityDialog() { + const [open, setOpen] = useState(false); + const utils = trpc.useUtils(); + + const { + register, + handleSubmit, + setError, + formState: { errors, isValid }, + reset, + } = useForm({ + resolver: zodResolver(formSchema), + mode: "onChange", + defaultValues: { + externalId: "", + meta: "", + }, + }); + + const createIdentity = trpc.identity.create.useMutation({ + onSuccess: (data) => { + toast.success("Identity created successfully", { + description: `Identity "${data.externalId}" has been created.`, + }); + // Invalidate queries to refetch the list + utils.identity.query.invalidate(); + setOpen(false); + reset(); + }, + onError: (error) => { + if (error.data?.code === "CONFLICT") { + setError("externalId", { + message: "An identity with this external ID already exists", + }); + } else { + toast.error("Failed to create identity", { + description: error.message || "An unexpected error occurred", + }); + } + }, + }); + + const onSubmit = (data: FormValues) => { + const meta = data.meta?.trim() ? JSON.parse(data.meta) : null; + createIdentity.mutate({ + externalId: data.externalId, + meta, + }); + }; + + return ( + <> + setOpen(true)}> + + Create Identity + + + + +
+ Create a new identity to associate with keys and rate limits +
+ + } + > +
+ + +
+ +