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
@@ -1,7 +1,7 @@
"use client";
import { trpc } from "@/lib/trpc/client";
import { collection } from "@/lib/collections";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button, DialogContainer, Input, toast } from "@unkey/ui";
import { Button, DialogContainer, Input } from "@unkey/ui";
import type { PropsWithChildren } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
Expand All @@ -23,14 +23,7 @@ type Props = PropsWithChildren<{
}>;

export const DeleteDialog = ({ isModalOpen, onOpenChange, overrideId, identifier }: Props) => {
const { ratelimit } = trpc.useUtils();

const {
register,
handleSubmit,
watch,
formState: { isSubmitting },
} = useForm<FormValues>({
const { register, handleSubmit, watch } = useForm<FormValues>({
mode: "onChange",
resolver: zodResolver(formSchema),
defaultValues: {
Expand All @@ -40,28 +33,9 @@ export const DeleteDialog = ({ isModalOpen, onOpenChange, overrideId, identifier

const isValid = watch("identifier") === identifier;

const deleteOverride = trpc.ratelimit.override.delete.useMutation({
onSuccess() {
toast.success("Override has been deleted", {
description: "Changes may take up to 60s to propagate globally",
});
onOpenChange(false);
ratelimit.overview.logs.query.invalidate();
ratelimit.logs.queryRatelimitTimeseries.invalidate();
},
onError(err) {
toast.error("Failed to delete override", {
description: err.message,
});
},
});

const onSubmit = async () => {
try {
await deleteOverride.mutateAsync({ id: overrideId });
} catch (error) {
console.error("Delete error:", error);
}
collection.ratelimitOverrides.delete(overrideId);
onOpenChange(false);
};

return (
Expand All @@ -77,8 +51,7 @@ export const DeleteDialog = ({ isModalOpen, onOpenChange, overrideId, identifier
variant="primary"
color="danger"
size="xlg"
disabled={!isValid || deleteOverride.isLoading || isSubmitting}
loading={deleteOverride.isLoading || isSubmitting}
disabled={!isValid}
className="w-full rounded-lg"
>
Delete Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
"use client";

import { trpc } from "@/lib/trpc/client";
import { collection } from "@/lib/collections";
import { zodResolver } from "@hookform/resolvers/zod";
import { CircleInfo } from "@unkey/icons";
import {
Badge,
Button,
DialogContainer,
FormInput,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
toast,
} from "@unkey/ui";
import { DuplicateKeyError } from "@tanstack/react-db";
import { Badge, Button, DialogContainer, FormInput } from "@unkey/ui";
import { useRouter } from "next/navigation";
import type { PropsWithChildren } from "react";
import { Controller, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import type { OverrideDetails } from "../types";

Expand All @@ -32,7 +22,6 @@ const overrideValidationSchema = z.object({
.int()
.min(1_000, "Duration must be at least 1 second (1000ms)")
.max(24 * 60 * 60 * 1000, "Duration cannot exceed 24 hours"),
async: z.enum(["unset", "sync", "async"]),
});

type FormValues = z.infer<typeof overrideValidationSchema>;
Expand All @@ -54,79 +43,53 @@ export const IdentifierDialog = ({
overrideDetails,
isLoading = false,
}: Props) => {
const { ratelimit } = trpc.useUtils();
const {
register,
handleSubmit,
control,
formState: { errors, isSubmitting },
setError,
} = useForm<FormValues>({
resolver: zodResolver(overrideValidationSchema),
defaultValues: {
identifier,
limit: overrideDetails?.limit ?? 10,
duration: overrideDetails?.duration ?? 60_000,
async:
overrideDetails?.async === undefined ? "unset" : overrideDetails.async ? "async" : "sync",
},
});

const update = trpc.ratelimit.override.update.useMutation({
onSuccess() {
toast.success("Limits have been updated", {
description: "Changes may take up to 60s to propagate globally",
});
onOpenChange(false);
ratelimit.overview.logs.query.invalidate();
},
onError(err) {
toast.error("Failed to update override", {
description: err.message,
});
},
});

const create = trpc.ratelimit.override.create.useMutation({
onSuccess() {
toast.success("Override has been created", {
description: "Changes may take up to 60s to propagate globally",
});
onOpenChange(false);
ratelimit.overview.logs.query.invalidate();
},
onError(err) {
toast.error("Failed to create override", {
description: err.message,
});
},
});
const router = useRouter();

const onSubmitForm = async (values: FormValues) => {
try {
const asyncValue = {
unset: undefined,
sync: false,
async: true,
}[values.async];

if (overrideDetails?.overrideId) {
await update.mutateAsync({
id: overrideDetails.overrideId,
limit: values.limit,
duration: values.duration,
async: Boolean(overrideDetails.async),
collection.ratelimitOverrides.update(overrideDetails.overrideId, (draft) => {
draft.limit = values.limit;
draft.duration = values.duration;
});
onOpenChange(false);
} else {
await create.mutateAsync({
// workaround until tanstack db throws on index violation
collection.ratelimitOverrides.forEach((override) => {
if (override.namespaceId === namespaceId && override.identifier === values.identifier) {
throw new DuplicateKeyError(override.id);
}
});
collection.ratelimitOverrides.insert({
namespaceId,
id: new Date().toISOString(), // gets replaced by backend
identifier: values.identifier,
limit: values.limit,
duration: values.duration,
async: asyncValue,
});
onOpenChange(false);
router.push(`/ratelimits/${namespaceId}/overrides`);
}
} catch (error) {
console.error("Form submission error:", error);
if (error instanceof DuplicateKeyError) {
setError("identifier", { type: "custom", message: "Identifier already exists" });
} else {
throw error;
}
}
};

Expand Down Expand Up @@ -190,31 +153,6 @@ export const IdentifierDialog = ({
</Badge>
}
/>

<Controller
control={control}
name="async"
render={({ field }) => (
<div className="space-y-1.5">
<div className="text-gray-11 text-[13px] flex items-center">Override Type</div>

<Select onValueChange={field.onChange} value={field.value}>
<SelectTrigger className="flex h-8 w-full items-center justify-between rounded-md bg-transparent px-3 py-2 text-[13px] border border-gray-4 focus:border focus:border-gray-4 hover:bg-gray-4 hover:border-gray-8 focus:bg-gray-4">
<SelectValue />
</SelectTrigger>
<SelectContent className="border-none">
<SelectItem value="unset">Don't override</SelectItem>
<SelectItem value="async">Async</SelectItem>
<SelectItem value="sync">Sync</SelectItem>
</SelectContent>
</Select>
<output className="text-gray-9 flex gap-2 items-center text-[13px]">
<CircleInfo size="md-regular" aria-hidden="true" />
<span>Override the mode, async is faster but slightly less accurate.</span>
</output>
</div>
)}
/>
</form>
</DialogContainer>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import { trpc } from "@/lib/trpc/client";
import { collection } from "@/lib/collections";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button, DialogContainer, Input, toast } from "@unkey/ui";
import { Button, DialogContainer, Input } from "@unkey/ui";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { z } from "zod";
Expand All @@ -20,7 +20,6 @@ type DeleteNamespaceProps = {
onOpenChange: (value: boolean) => void;
namespace: {
id: string;
workspaceId: string;
name: string;
};
};
Expand All @@ -31,39 +30,20 @@ export const DeleteNamespaceDialog = ({
namespace,
}: DeleteNamespaceProps) => {
const router = useRouter();
const {
register,
handleSubmit,
watch,
formState: { isSubmitting },
} = useForm<FormValues>({
const { register, handleSubmit, watch } = useForm<FormValues>({
mode: "onChange",
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
},
});
const trpcUtils = trpc.useUtils();
const isValid = watch("name") === namespace.name;

const deleteNamespace = trpc.ratelimit.namespace.delete.useMutation({
onSuccess() {
toast.success("Namespace Deleted", {
description: "Your namespace and all its overridden identifiers have been deleted.",
});
trpcUtils.ratelimit.namespace.query.invalidate();
router.push("/ratelimits");
onOpenChange(false);
},
onError(err) {
toast.error("Failed to delete namespace", {
description: err.message,
});
},
});

const onSubmit = async () => {
await deleteNamespace.mutateAsync({ namespaceId: namespace.id });
collection.ratelimitNamespaces.delete(namespace.id);
router.push("/ratelimits");

//await deleteNamespace.mutateAsync({ namespaceId: namespace.id });
};
return (
<DialogContainer
Expand All @@ -78,8 +58,7 @@ export const DeleteNamespaceDialog = ({
variant="primary"
color="danger"
size="xlg"
disabled={!isValid || deleteNamespace.isLoading || isSubmitting}
loading={deleteNamespace.isLoading || isSubmitting}
disabled={!isValid}
className="w-full rounded-lg"
>
Delete Namespace
Expand Down
Loading