Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type { StepNamesFrom } from "@unkey/ui";
import type { SectionState } from "./types";

import { MetadataSetup } from "@/components/dashboard/metadata/metadata-setup";
import { RatelimitSetup } from "@/components/dashboard/ratelimits/ratelimit-setup";
import { UsageSetup } from "./components/credits-setup";
import { ExpirationSetup } from "./components/expiration-setup";
import { GeneralSetup } from "./components/general-setup";
import { RatelimitSetup } from "./components/ratelimit-setup";

export const UNNAMED_KEY = "Unnamed Key" as const;
export const SECTIONS = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { RatelimitSetup } from "@/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/components/ratelimit-setup";
import {
type RatelimitFormValues,
ratelimitSchema,
} from "@/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/create-key.schema";
import { RatelimitSetup } from "@/components/dashboard/ratelimits/ratelimit-setup";
import type { ActionComponentProps } from "@/components/logs/table-action.popover";
import { useEditRatelimits } from "@/hooks/use-edit-ratelimits";
import { usePersistedForm } from "@/hooks/use-persisted-form";
import type { RatelimitFormValues } from "@/lib/schemas/ratelimit";
import { ratelimitSchema } from "@/lib/schemas/ratelimit";
import type { KeyDetails } from "@/lib/trpc/routers/api/keys/query-api-keys/schema";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button, DialogContainer } from "@unkey/ui";
import { useEffect } from "react";
import { FormProvider } from "react-hook-form";
import { useEditRatelimits } from "../hooks/use-edit-ratelimits";
import { KeyInfo } from "../key-info";
import { getKeyRatelimitsDefaults } from "./utils";

Expand Down Expand Up @@ -49,7 +47,7 @@ export const EditRatelimits = ({ keyDetails, isOpen, onClose }: EditRatelimitsPr
}
}, [isOpen, loadSavedValues]);

const key = useEditRatelimits(() => {
const key = useEditRatelimits("key", () => {
reset(getKeyRatelimitsDefaults(keyDetails));
clearPersistedData();
onClose();
Expand All @@ -59,10 +57,7 @@ export const EditRatelimits = ({ keyDetails, isOpen, onClose }: EditRatelimitsPr
try {
await key.mutateAsync({
keyId: keyDetails.id,
ratelimit: {
enabled: data.ratelimit.enabled,
data: data.ratelimit.data,
},
ratelimit: data.ratelimit,
});
} catch {
// `useEditRatelimits` already shows a toast, but we still need to
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"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,
NavigableDialogBody,
NavigableDialogContent,
NavigableDialogFooter,
NavigableDialogHeader,
NavigableDialogNav,
NavigableDialogRoot,
toast,
} from "@unkey/ui";
import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { SECTIONS } from "./create-identity.constants";
import { type FormValues, formSchema, getDefaultValues } from "./create-identity.schema";

export function CreateIdentityDialog() {
const [open, setOpen] = useState(false);
const utils = trpc.useUtils();

const methods = useForm<FormValues>({
resolver: zodResolver(formSchema),
mode: "onChange",
defaultValues: getDefaultValues(),
});

const {
handleSubmit,
setError,
formState: { isValid },
reset,
} = methods;

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(getDefaultValues());
},
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.metadata?.enabled && data.metadata.data ? JSON.parse(data.metadata.data) : null;
const ratelimits =
data.ratelimit?.enabled && data.ratelimit.data ? data.ratelimit.data : undefined;
createIdentity.mutate({
externalId: data.externalId,
meta,
ratelimits,
});
};

return (
<>
<NavbarActionButton title="Create Identity" onClick={() => setOpen(true)}>
<Plus iconSize="md-medium" />
Create Identity
</NavbarActionButton>

<FormProvider {...methods}>
<form id="create-identity-form" onSubmit={handleSubmit(onSubmit)}>
<NavigableDialogRoot
isOpen={open}
onOpenChange={setOpen}
dialogClassName="w-[90%] md:w-[70%] lg:w-[70%] xl:w-[50%] 2xl:w-[45%] max-w-[940px] max-h-[90vh]"
>
<NavigableDialogHeader
title="Create Identity"
subTitle="Create a new identity to associate with keys and rate limits"
/>
<NavigableDialogBody>
<NavigableDialogNav
items={SECTIONS.map((section) => ({
id: section.id,
label: section.label,
icon: section.icon,
}))}
initialSelectedId="general"
/>
<NavigableDialogContent
items={SECTIONS.map((section) => ({
id: section.id,
content: section.content(),
}))}
/>
</NavigableDialogBody>
<NavigableDialogFooter>
<div className="flex justify-center items-center w-full">
<div className="flex flex-col items-center justify-center w-2/3 gap-2">
<Button
type="submit"
form="create-identity-form"
variant="primary"
size="xlg"
className="w-full rounded-lg"
disabled={!isValid || createIdentity.isLoading}
loading={createIdentity.isLoading}
>
Create Identity
</Button>
<div className="text-gray-9 text-xs">
Create an identity to group keys and manage permissions
</div>
</div>
</div>
</NavigableDialogFooter>
</NavigableDialogRoot>
</form>
</FormProvider>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MetadataSetup } from "@/components/dashboard/metadata/metadata-setup";
import { RatelimitSetup } from "@/components/dashboard/ratelimits/ratelimit-setup";
import { Code, Fingerprint, Gauge } from "@unkey/icons";
import type { StepNamesFrom } from "@unkey/ui";
import { GeneralSetup } from "./general-setup";

export const SECTIONS = [
{
id: "general",
label: "General Setup",
icon: Fingerprint,
content: () => <GeneralSetup />,
},
{
id: "ratelimit",
label: "Ratelimit",
icon: Gauge,
content: () => <RatelimitSetup entityType="identity" />,
},
{
id: "metadata",
label: "Metadata",
icon: Code,
content: () => <MetadataSetup entityType="identity" />,
},
] as const;

export type DialogSectionName = StepNamesFrom<typeof SECTIONS>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { metadataSchema } from "@/lib/schemas/metadata";
import { ratelimitSchema } from "@/lib/schemas/ratelimit";
import { z } from "zod";

export 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"),
})
.merge(metadataSchema)
.merge(ratelimitSchema);

export type FormValues = z.infer<typeof formSchema>;

export const getDefaultValues = (): FormValues => ({
externalId: "",
metadata: {
enabled: false,
},
ratelimit: {
enabled: false,
data: [
{
name: "default",
limit: 10,
refillInterval: 1000,
autoApply: true,
},
],
},
});
Loading
Loading