Skip to content
72 changes: 52 additions & 20 deletions apps/dashboard/app/(app)/settings/root-keys/new/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,40 @@ import { Label } from "@/components/ui/label";
import { toast } from "@/components/ui/toaster";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { trpc } from "@/lib/trpc/client";
import type { UnkeyPermission } from "@unkey/rbac";
import { unkeyPermissionValidation, type UnkeyPermission } from "@unkey/rbac";
import { ChevronRight } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useEffect, useState } from "react";
import { apiPermissions, workspacePermissions } from "../[keyId]/permissions/permissions";
import { createParser, parseAsArrayOf, useQueryState } from "nuqs";

type Props = {
apis: {
id: string;
name: string;
}[];
};

const parseAsUnkeyPermission = createParser({
parse(queryValue) {
const { success, data } = unkeyPermissionValidation.safeParse(queryValue);
return success ? data : null;
},
serialize: String,
});

export const Client: React.FC<Props> = ({ apis }) => {
const router = useRouter();
const [name, setName] = useState<string | undefined>(undefined);
const [selectedPermissions, setSelectedPermissions] = useState<UnkeyPermission[]>([]);

const [selectedPermissions, setSelectedPermissions] = useQueryState(
"permissions",
parseAsArrayOf(parseAsUnkeyPermission).withDefault([]).withOptions({
history: "push",
shallow: false, // otherwise server components won't notice the change
clearOnDefault: true,
})
);

const key = trpc.rootKey.create.useMutation({
onError(err: { message: string }) {
Expand Down Expand Up @@ -67,15 +85,7 @@ export const Client: React.FC<Props> = ({ apis }) => {
});
};

type CardStates = {
[key: string]: boolean;
};

const initialCardStates: CardStates = {};
apis.forEach((api) => {
initialCardStates[api.id] = false;
});
const [cardStatesMap, setCardStatesMap] = useState(initialCardStates);
const [cardStatesMap, setCardStatesMap] = useState<Record<string, boolean>>({});

const toggleCard = (apiId: string) => {
setCardStatesMap((prevStates) => ({
Expand All @@ -84,6 +94,26 @@ export const Client: React.FC<Props> = ({ apis }) => {
}));
};

useEffect(() => {
const initialSelectedApiSet = new Set<string>();
selectedPermissions.forEach((permission) => {
const apiId = permission.split('.')[1] ?? ''; // Extract API ID
if (apiId.length) initialSelectedApiSet.add(apiId);
});

const initialCardStates: Record<string, boolean> = {};
apis.forEach((api) => {
initialCardStates[api.id] = initialSelectedApiSet.has(api.id); // O(1) check
});

// We use a Set to gather unique API IDs, enabling O(1) membership checks.
// This avoids the O(m * n) complexity of repeatedly iterating over selectedPermissions
// for each API, reducing the overall complexity to O(n + m) and improving performance
// for large data sets.

setCardStatesMap(initialCardStates);
}, []); // Execute ones on the first load

return (
<div className="flex flex-col gap-4">
<Card>
Expand Down Expand Up @@ -309,15 +339,17 @@ const PermissionToggle: React.FC<PermissionToggleProps> = ({
<div className="flex flex-col sm:items-center gap-1 mb-2 sm:flex-row sm:gap-0 sm:mb-0">
<div className="w-1/3">
<Tooltip>
<TooltipTrigger className="flex items-center gap-2">
<Checkbox
checked={checked}
onClick={() => {
setChecked(!checked);
}}
/>
<TooltipTrigger asChild>
<div className="flex items-center gap-2">
<Checkbox
checked={checked}
onCheckedChange={() => {
setChecked(!checked);
}}
/>

<Label className="text-xs text-content">{label}</Label>
<Label className="text-xs text-content">{label}</Label>
</div>
</TooltipTrigger>
<TooltipContent className="flex items-center gap-2">
<span className="font-mono text-sm font-medium">{permissionName}</span>
Expand Down