diff --git a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx index b057986619..9ed8d82c58 100644 --- a/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx +++ b/apps/dashboard/app/(app)/settings/root-keys/new/client.tsx @@ -22,11 +22,13 @@ 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; @@ -34,10 +36,26 @@ type Props = { }[]; }; +const parseAsUnkeyPermission = createParser({ + parse(queryValue) { + const { success, data } = unkeyPermissionValidation.safeParse(queryValue); + return success ? data : null; + }, + serialize: String, +}); + export const Client: React.FC = ({ apis }) => { const router = useRouter(); const [name, setName] = useState(undefined); - const [selectedPermissions, setSelectedPermissions] = useState([]); + + 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 }) { @@ -67,15 +85,7 @@ export const Client: React.FC = ({ 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>({}); const toggleCard = (apiId: string) => { setCardStatesMap((prevStates) => ({ @@ -84,6 +94,26 @@ export const Client: React.FC = ({ apis }) => { })); }; + useEffect(() => { + const initialSelectedApiSet = new Set(); + selectedPermissions.forEach((permission) => { + const apiId = permission.split('.')[1] ?? ''; // Extract API ID + if (apiId.length) initialSelectedApiSet.add(apiId); + }); + + const initialCardStates: Record = {}; + 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 (
@@ -309,15 +339,17 @@ const PermissionToggle: React.FC = ({
- - { - setChecked(!checked); - }} - /> + +
+ { + setChecked(!checked); + }} + /> - + +
{permissionName}