Skip to content
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCreateIdentity } from "@/app/(app)/apis/[apiId]/_components/create-key/hooks/use-create-identity";
import { useFetchIdentities } from "@/app/(app)/apis/[apiId]/_components/create-key/hooks/use-fetch-identities";
import { createIdentityOptions } from "@/app/(app)/apis/[apiId]/_components/create-key/hooks/use-fetch-identities/create-identity-options";
import { useCreateIdentity } from "@/app/(app)/[workspace]/apis/[apiId]/_components/create-key/hooks/use-create-identity";
import { useFetchIdentities } from "@/app/(app)/[workspace]/apis/[apiId]/_components/create-key/hooks/use-fetch-identities";
import { createIdentityOptions } from "@/app/(app)/[workspace]/apis/[apiId]/_components/create-key/hooks/use-fetch-identities/create-identity-options";
import { FormCombobox } from "@/components/ui/form-combobox";
import type { Identity } from "@unkey/db";
import { TriangleWarning2 } from "@unkey/icons";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { ConfirmPopover } from "@/components/confirmation-popover";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { useWorkspace } from "@/providers/workspace-provider";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Guard against missing workspace slug to avoid navigating to /undefined/...

router.push builds /${workspace?.slug}/...; if the provider isn't ready, this becomes /undefined/.... Add a fallback from route params and fail gracefully.

Apply this diff:

 import { useEffect, useRef, useState } from "react";
+import { useParams } from "next/navigation";
@@
-  const { workspace } = useWorkspace();
+  const { workspace } = useWorkspace();
+  const params = useParams<{ workspace: string }>();
@@
-          router.push(`/${workspace?.slug}/apis/${apiId}/keys/${keyspaceId}/${keyData.id}`);
+          {
+            const slug = workspace?.slug ?? params?.workspace;
+            if (!slug) {
+              toast.error("Failed to Navigate", {
+                description: "Workspace is not loaded yet. Please try again.",
+              });
+              return;
+            }
+            router.push(
+              `/${encodeURIComponent(slug)}/apis/${encodeURIComponent(apiId)}/keys/${encodeURIComponent(
+                keyspaceId,
+              )}/${encodeURIComponent(keyData.id)}`,
+            );
+          }

Run a quick scan for other potentially unsafe patterns:

Also applies to: 28-28, 94-95


🏁 Script executed:

#!/bin/bash
# Find href/router.push that interpolate workspace?.slug directly
rg -nP --type=tsx --type=ts '/\$\{workspace\?\.\s*slug\}/' -S

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf "Repo root: %s\n\n" "$(pwd)"

GLOB='**/*.{ts,tsx,js,jsx,mdx}'
IGNORE=(--glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' --glob '!.next/**' --glob '!**/vendor/**')

echo "Checking specific file from the review..."
FILE='apps/dashboard/app/(app)/[workspace]/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx'
if [ -f "$FILE" ]; then
  echo "FOUND: $FILE"
  sed -n '1,240p' "$FILE" || true
else
  echo "NOT FOUND: $FILE"
fi
echo

echo "1) Searching for literal '${workspace?.slug'..."
rg -nF --hidden -S '${workspace?.slug' "${IGNORE[@]}" --glob "$GLOB" || true
echo

echo "2) Searching for literal '${workspace.slug'..."
rg -nF --hidden -S '${workspace.slug' "${IGNORE[@]}" --glob "$GLOB" || true
echo

echo "3) Searching for '/${workspace?.slug' and '/${workspace.slug' (template starts)..."
rg -nF --hidden -S '/${workspace?.slug' "${IGNORE[@]}" --glob "$GLOB" || true
rg -nF --hidden -S '/${workspace.slug' "${IGNORE[@]}" --glob "$GLOB" || true
echo

echo "4) Searching for workspace?.slug (anywhere)..."
rg -nF --hidden -S 'workspace?.slug' "${IGNORE[@]}" --glob "$GLOB" || true
echo

echo "5) Searching for router.push occurrences (showing context)..."
rg -n -C3 --hidden -S 'router.push' "${IGNORE[@]}" --glob "$GLOB" || true
echo

echo "6) Searching for href={`/${ occurrences (common pattern)..."
rg -nF --hidden -S 'href={`/${' "${IGNORE[@]}" --glob "$GLOB" || true
echo

echo "7) Searching for concatenations like '/' + workspace..."
rg -nF --hidden -S "'/' + workspace" "${IGNORE[@]}" --glob "$GLOB" || true
rg -nF --hidden -S '\"/\" + workspace' "${IGNORE[@]}" --glob "$GLOB" || true
echo

echo "Done."

Length of output: 9171


Guard against missing workspace slug when navigating (must fix)

router.push builds /${workspace?.slug}/... which becomes /undefined/... if the provider isn't ready — add a fallback to route params and fail gracefully.

File: apps/dashboard/app/(app)/[workspace]/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx

 import { useEffect, useRef, useState } from "react";
+import { useParams } from "next/navigation";
@@
-  const { workspace } = useWorkspace();
+  const { workspace } = useWorkspace();
+  const params = useParams<{ workspace: string }>();
@@
-          router.push(`/${workspace?.slug}/apis/${apiId}/keys/${keyspaceId}/${keyData.id}`);
+          {
+            const slug = workspace?.slug ?? params?.workspace;
+            if (!slug) {
+              toast.error("Failed to Navigate", {
+                description: "Workspace is not loaded yet. Please try again.",
+              });
+              return;
+            }
+            router.push(
+              `/${encodeURIComponent(slug)}/apis/${encodeURIComponent(apiId)}/keys/${encodeURIComponent(
+                keyspaceId,
+              )}/${encodeURIComponent(keyData.id)}`,
+            );
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useWorkspace } from "@/providers/workspace-provider";
import { useEffect, useRef, useState } from "react";
import { useParams } from "next/navigation";
import { useWorkspace } from "@/providers/workspace-provider";
const { workspace } = useWorkspace();
const params = useParams<{ workspace: string }>();
{
const slug = workspace?.slug ?? params?.workspace;
if (!slug) {
toast.error("Failed to Navigate", {
description: "Workspace is not loaded yet. Please try again.",
});
return;
}
router.push(
`/${encodeURIComponent(slug)}/apis/${encodeURIComponent(apiId)}/keys/${encodeURIComponent(
keyspaceId,
)}/${encodeURIComponent(keyData.id)}`,
);
}
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/[workspace]/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx
around line 5, the router.push call constructs paths using workspace?.slug which
can be undefined when the provider isn't ready; change the navigation to first
resolve a safe slug (e.g. const slug = workspace?.slug ?? params?.workspace ??
router.query?.workspace ?? '') and if slug is empty fail gracefully (return
early, show an error/toast or disable the button) so you never push a path
containing "undefined"; build the path using the safe slug (and
encodeURIComponent) and only call router.push when slug is present.

import { ArrowRight, Check, Key2, Plus } from "@unkey/icons";
import { Button, InfoTooltip, toast } from "@unkey/ui";
import { useRouter } from "next/navigation";
Expand All @@ -24,13 +25,13 @@ export const KeyCreatedSuccessDialog = ({
keyspaceId?: string | null;
onCreateAnother?: () => void;
}) => {
const { workspace } = useWorkspace();
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const [pendingAction, setPendingAction] = useState<
"close" | "create-another" | "go-to-details" | null
>(null);
const dividerRef = useRef<HTMLDivElement>(null);
const router = useRouter();

// Prevent accidental tab/window close when dialog is open
useEffect(() => {
if (!isOpen) {
Expand Down Expand Up @@ -90,7 +91,7 @@ export const KeyCreatedSuccessDialog = ({
});
return;
}
router.push(`/apis/${apiId}/keys/${keyspaceId}/${keyData.id}`);
router.push(`/${workspace?.slug}/apis/${apiId}/keys/${keyspaceId}/${keyData.id}`);
break;

default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { SecretKey } from "@/app/(app)/apis/[apiId]/_components/create-key/components/secret-key";
import { SecretKey } from "@/app/(app)/[workspace]/apis/[apiId]/_components/create-key/components/secret-key";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { CircleInfo } from "@unkey/icons";
import { Code, CopyButton, VisibleButton } from "@unkey/ui";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { DEFAULT_DRAGGABLE_WIDTH } from "@/app/(app)/logs/constants";
import { DEFAULT_DRAGGABLE_WIDTH } from "@/app/(app)/[workspace]/logs/constants";
import { ResizablePanel } from "@/components/logs/details/resizable-panel";
import { useWorkspace } from "@/providers/workspace-provider";
import type { KeysOverviewLog } from "@unkey/clickhouse/src/keys/keys";
import { TimestampInfo } from "@unkey/ui";
import Link from "next/link";
Expand Down Expand Up @@ -37,8 +38,8 @@ export const KeysOverviewLogDetails = ({
setSelectedLog,
apiId,
}: KeysOverviewLogDetailsProps) => {
const { workspace } = useWorkspace();
const panelStyle = useMemo(() => createPanelStyle(distanceToTop), [distanceToTop]);

if (!log) {
return null;
}
Expand Down Expand Up @@ -68,7 +69,7 @@ export const KeysOverviewLogDetails = ({
<Link
title={`View details for ${log.key_id}`}
className="font-mono underline decoration-dotted"
href={`/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`}
href={`/${workspace?.slug}/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`}
>
<div className="font-mono font-medium truncate">{log.key_id}</div>
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { shortenId } from "@/lib/shorten-id";
import { cn } from "@/lib/utils";
import { useWorkspace } from "@/providers/workspace-provider";
import type { KeysOverviewLog } from "@unkey/clickhouse/src/keys/keys";
import { TriangleWarning2 } from "@unkey/icons";
import { InfoTooltip, Loading } from "@unkey/ui";
Expand Down Expand Up @@ -44,11 +45,11 @@ const getWarningMessage = (severity: string, errorRate: number) => {
};

export const KeyIdentifierColumn = ({ log, apiId, onNavigate }: KeyIdentifierColumnProps) => {
const { workspace } = useWorkspace();
const router = useRouter();
const errorPercentage = getErrorPercentage(log);
const severity = getErrorSeverity(log);
const hasErrors = severity !== "none";

const [isNavigating, setIsNavigating] = useState(false);

const handleLinkClick = useCallback(
Expand All @@ -58,9 +59,11 @@ export const KeyIdentifierColumn = ({ log, apiId, onNavigate }: KeyIdentifierCol

onNavigate?.();

router.push(`/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`);
router.push(
`/${workspace?.slug}/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`,
);
Comment on lines +62 to +64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against undefined workspace slug/key_auth_id to prevent broken URLs.

Optional chaining inside template literals will stringify to "undefined", yielding paths like "/undefined/apis/...". Add a runtime guard and make the link href resilient.

Apply these diffs:

-      router.push(
-        `/${workspace?.slug}/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`,
-      );
+      if (!workspace?.slug || !log.key_details?.key_auth_id) {
+        setIsNavigating(false);
+        return;
+      }
+      router.push(
+        `/${workspace.slug}/apis/${apiId}/keys/${log.key_details.key_auth_id}/${log.key_id}`,
+      );
-        href={`/${workspace?.slug}/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`}
+        href={
+          workspace?.slug && log.key_details?.key_auth_id
+            ? `/${workspace.slug}/apis/${apiId}/keys/${log.key_details.key_auth_id}/${log.key_id}`
+            : "#"
+        }

Also applies to: 89-89

},
[apiId, log.key_id, log.key_details?.key_auth_id, onNavigate, router.push],
[apiId, log.key_id, log.key_details?.key_auth_id, onNavigate, router.push, workspace?.slug],
);

return (
Expand All @@ -83,7 +86,7 @@ export const KeyIdentifierColumn = ({ log, apiId, onNavigate }: KeyIdentifierCol
<Link
title={`View details for ${log.key_id}`}
className="font-mono group-hover:underline decoration-dotted"
href={`/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`}
href={`/${workspace?.slug}/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`}
onClick={handleLinkClick}
>
<div className="font-mono font-medium truncate flex items-center">
Expand Down
233 changes: 233 additions & 0 deletions apps/dashboard/app/(app)/[workspace]/apis/[apiId]/api-id-navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
"use client";

import { QuickNavPopover } from "@/components/navbar-popover";
import { NavbarActionButton } from "@/components/navigation/action-button";
import { Navbar } from "@/components/navigation/navbar";
import { useIsMobile } from "@/hooks/use-mobile";
import { trpc } from "@/lib/trpc/client";
import { useWorkspace } from "@/providers/workspace-provider";
import type { Workspace } from "@unkey/db";
import { ChevronExpandY, Nodes, Plus, TaskUnchecked } from "@unkey/icons";
import { CreateKeyDialog } from "./_components/create-key";

// Types for better type safety
interface ApiLayoutData {
currentApi: {
id: string;
name: string;
workspaceId: string;
keyAuthId: string | null;
keyspaceDefaults: {
prefix?: string;
bytes?: number;
} | null;
deleteProtection: boolean | null;
ipWhitelist: string | null;
};
workspaceApis: Array<{
id: string;
name: string;
}>;
keyAuth: {
id: string;
defaultPrefix: string | null;
defaultBytes: number | null;
sizeApprox: number;
} | null;
workspace: {
id: string;
ipWhitelist: boolean;
};
}

interface ApisNavbarProps {
apiId: string;
keyspaceId?: string;
keyId?: string;
activePage?: {
href: string;
text: string;
};
}

interface LoadingNavbarProps {
workspace: Workspace | null;
}

interface NavbarContentProps {
apiId: string;
keyspaceId?: string;
keyId?: string;
activePage?: {
href: string;
text: string;
};
workspace: Workspace | null;
isMobile: boolean;
layoutData: ApiLayoutData;
}

// Loading state component
const LoadingNavbar = ({ workspace }: LoadingNavbarProps) => (
<Navbar>
<Navbar.Breadcrumbs icon={<Nodes />}>
<Navbar.Breadcrumbs.Link href={`/${workspace?.slug}/apis`}>APIs</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link href="#" isIdentifier className="group" noop>
<div className="h-6 w-20 bg-grayA-3 rounded animate-pulse transition-all " />
</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link href="#" noop active>
<div className="hover:bg-gray-3 rounded-lg flex items-center gap-1 p-1">
<div className="h-6 w-16 bg-grayA-3 rounded animate-pulse transition-all " />
<ChevronExpandY className="size-4" />
</div>
</Navbar.Breadcrumbs.Link>
</Navbar.Breadcrumbs>
<Navbar.Actions>
<NavbarActionButton disabled>
<Plus />
Create new key
</NavbarActionButton>
<div className="h-7 bg-grayA-2 border border-gray-6 rounded-md animate-pulse px-3 flex gap-2 items-center justify-center w-[190px] transition-all ">
<div className="h-3 w-[190px] bg-grayA-3 rounded" />
<div>
<TaskUnchecked size="md-regular" className="!size-4" />
</div>
</div>
</Navbar.Actions>
</Navbar>
);
Comment on lines +70 to +98
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

Avoid generating broken links when workspace slug is missing; centralize href building

Current code can emit /undefined/... during load. Build hrefs via a helper and mark links noop when slug is absent.

+// Workspace-safe href builder
+const wsHref = (slug?: string, path = "") => (slug ? `/${slug}${path}` : "#");

 // Loading state component
 const LoadingNavbar = ({ workspace }: LoadingNavbarProps) => (
   <Navbar>
     <Navbar.Breadcrumbs icon={<Nodes />}>
-      <Navbar.Breadcrumbs.Link href={`/${workspace?.slug}/apis`}>APIs</Navbar.Breadcrumbs.Link>
+      <Navbar.Breadcrumbs.Link
+        href={wsHref(workspace?.slug, "/apis")}
+        noop={!workspace}
+      >
+        APIs
+      </Navbar.Breadcrumbs.Link>
       <Navbar.Breadcrumbs.Link href="#" isIdentifier className="group" noop>
         <div className="h-6 w-20 bg-grayA-3 rounded animate-pulse transition-all " />
       </Navbar.Breadcrumbs.Link>
   const { currentApi } = layoutData;

   // Define base path for API navigation
-  const base = `/${workspace?.slug}/apis/${currentApi.id}`;
+  const slug = workspace?.slug;
+  const base = wsHref(slug, `/apis/${currentApi.id}`);

   // Create navigation items for QuickNavPopover
   const navigationItems = [
     {
       id: "requests",
       label: "Requests",
-      href: `/${workspace?.slug}/apis/${currentApi.id}`,
+      href: wsHref(slug, `/apis/${currentApi.id}`),
     },
   ];

   // Add Keys navigation if keyAuthId exists
   if (currentApi.keyAuthId) {
     navigationItems.push({
       id: "keys",
       label: "Keys",
-      href: `/${workspace?.slug}/apis/${currentApi.id}/keys/${currentApi.keyAuthId}`,
+      href: wsHref(slug, `/apis/${currentApi.id}/keys/${currentApi.keyAuthId}`),
     });
   }

   // Add Settings navigation
   navigationItems.push({
     id: "settings",
     label: "Settings",
-    href: `/${workspace?.slug}/apis/${currentApi.id}/settings`,
+    href: wsHref(slug, `/apis/${currentApi.id}/settings`),
   });
-          <Navbar.Breadcrumbs.Link
-            href={`/${workspace?.slug}/apis`}
-            className={isMobile ? "hidden" : "max-md:hidden"}
-          >
+          <Navbar.Breadcrumbs.Link
+            href={wsHref(slug, "/apis")}
+            className={isMobile ? "hidden" : "max-md:hidden"}
+            noop={!slug}
+          >
             APIs
           </Navbar.Breadcrumbs.Link>
           <Navbar.Breadcrumbs.Link
             href={base}
             isIdentifier
             className={isMobile ? "hidden" : "group max-md:hidden"}
             noop
           >
             <div className="text-accent-10 group-hover:text-accent-12">{currentApi.name}</div>
           </Navbar.Breadcrumbs.Link>
-          <Navbar.Breadcrumbs.Link href={activePage?.href ?? ""} noop active={!shouldFetchKey}>
+          <Navbar.Breadcrumbs.Link
+            href={activePage?.href ?? wsHref(slug, `/apis/${currentApi.id}`)}
+            noop={!slug}
+            active={!shouldFetchKey}
+          >
             <QuickNavPopover items={navigationItems} shortcutKey="M">
               <div className="hover:bg-gray-3 rounded-lg flex items-center gap-1 p-1">
                 {activePage?.text ?? ""}
                 <ChevronExpandY className="size-4" />
               </div>
             </QuickNavPopover>
           </Navbar.Breadcrumbs.Link>

Also applies to: 119-146, 151-173, 165-172


🏁 Script executed:

#!/bin/bash
# Find potentially unsafe hrefs using `workspace?.slug` that could yield `/undefined/...`
rg -nP --type=ts --type=tsx -C2 'href\s*=\s*{`/\$\{workspace\?\.\s*slug\}[^`]*`}'

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the targeted file if present
FILES=$(find . -type f -name 'api-id-navbar.tsx' 2>/dev/null || true)
if [ -n "$FILES" ]; then
  for f in $FILES; do
    echo "=== FILE: $f ==="
    sed -n '1,240p' "$f" || true
  done
else
  echo "No api-id-navbar.tsx found."
fi

echo "=== Search: occurrences of workspace?.slug ==="
rg -n --hidden -S 'workspace\?\.\s*slug' || true

echo "=== Search: template usages with \${workspace?....} ==="
rg -n --hidden -S '\$\{workspace\?\.' || true

echo "=== Search: href template literals (look for backtick hrefs) ==="
rg -n --hidden -S 'href\s*=\s*{`' || true
rg -n --hidden -S 'href\s*=\s*{`[^`]*\$\{workspace' || true

echo "=== Fallback: any '/${workspace?.' occurrences ==="
rg -n --hidden -S '/\$\{workspace\?\.' || true

echo "=== Done ==="

Length of output: 27261


Centralize workspace-safe href builder and mark links noop when slug is missing

Template literals like /${workspace?.slug}/... render as /undefined/... when slug is absent — add a small helper (e.g. const wsHref = (slug?: string, path="") => slug ? \/${slug}${path}` : "#") and set noop={!slug}on Navbar links. Fix LoadingNavbar and NavbarContent in apps/dashboard/app/(app)/[workspace]/apis/[apiId]/api-id-navbar.tsx (around lines ~74, 120–152) and sweep other/${workspace?.slug}` occurrences across the repo.

🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/[workspace]/apis/[apiId]/api-id-navbar.tsx around
lines 70–98 and 120–152, template literals like `/${workspace?.slug}/...` can
produce `/undefined/...` when slug is absent; add a small helper near the top of
the file (e.g. wsHref = (slug?: string, path = "") => slug ? `/${slug}${path}` :
"#") and use wsHref(workspace?.slug, "/apis") (and other paths) instead of raw
template literals, set noop={!workspace?.slug} (or noop={!slug}) on
Navbar.Breadcrumbs.Link and other Navbar links when slug is missing, and update
LoadingNavbar and NavbarContent uses accordingly; then sweep the repo for other
`/${workspace?.slug}` occurrences and replace with wsHref + noop guards.


// Main navbar content component
const NavbarContent = ({
keyspaceId,
keyId,
activePage,
workspace,
isMobile,
layoutData,
}: NavbarContentProps) => {
const shouldFetchKey = Boolean(keyspaceId && keyId);

// If we expected to find a key but this component doesn't handle key details,
// we should handle this at a higher level or in a different component
if (shouldFetchKey) {
console.warn("Key fetching logic should be handled at a higher level");
}

const { currentApi } = layoutData;

// Define base path for API navigation
const base = `/${workspace?.slug}/apis/${currentApi.id}`;

// Create navigation items for QuickNavPopover
const navigationItems = [
{
id: "requests",
label: "Requests",
href: `/${workspace?.slug}/apis/${currentApi.id}`,
},
];

// Add Keys navigation if keyAuthId exists
if (currentApi.keyAuthId) {
navigationItems.push({
id: "keys",
label: "Keys",
href: `/${workspace?.slug}/apis/${currentApi.id}/keys/${currentApi.keyAuthId}`,
});
}

// Add Settings navigation
navigationItems.push({
id: "settings",
label: "Settings",
href: `/${workspace?.slug}/apis/${currentApi.id}/settings`,
});

return (
<div className="w-full">
<Navbar className="w-full flex justify-between">
<Navbar.Breadcrumbs className="flex-1 w-full" icon={<Nodes />}>
<Navbar.Breadcrumbs.Link
href={`/${workspace?.slug}/apis`}
className={isMobile ? "hidden" : "max-md:hidden"}
>
APIs
</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link
href={base}
isIdentifier
className={isMobile ? "hidden" : "group max-md:hidden"}
noop
>
<div className="text-accent-10 group-hover:text-accent-12">{currentApi.name}</div>
</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link href={activePage?.href ?? ""} noop active={!shouldFetchKey}>
<QuickNavPopover items={navigationItems} shortcutKey="M">
<div className="hover:bg-gray-3 rounded-lg flex items-center gap-1 p-1">
{activePage?.text ?? ""}
<ChevronExpandY className="size-4" />
</div>
</QuickNavPopover>
</Navbar.Breadcrumbs.Link>
</Navbar.Breadcrumbs>
{layoutData.keyAuth ? (
<CreateKeyDialog
keyspaceId={layoutData.keyAuth.id}
apiId={currentApi.id}
copyIdValue={currentApi.id}
keyspaceDefaults={currentApi.keyspaceDefaults}
/>
) : (
<NavbarActionButton disabled>
<Plus />
Create new key
</NavbarActionButton>
)}
</Navbar>
</div>
);
};

// Main component
export const ApisNavbar = ({ apiId, keyspaceId, keyId, activePage }: ApisNavbarProps) => {
const { workspace } = useWorkspace();
const isMobile = useIsMobile();

// Only make the query if we have a valid apiId
const {
data: layoutData,
isLoading,
error,
} = trpc.api.queryApiKeyDetails.useQuery(
{ apiId },
{
enabled: Boolean(apiId), // Only run query if apiId exists
retry: 3,
retryDelay: 1000,
},
);

// Show loading state while fetching data
if (isLoading || !layoutData) {
return <LoadingNavbar workspace={workspace} />;
}

// Handle error state
if (error) {
console.error("Failed to fetch API layout data:", error);
return <LoadingNavbar workspace={workspace} />;
}

return (
<NavbarContent
apiId={apiId}
keyspaceId={keyspaceId}
keyId={keyId}
activePage={activePage}
workspace={workspace}
isMobile={isMobile}
layoutData={layoutData}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OUTCOME_BACKGROUND_COLORS } from "@/app/(app)/apis/[apiId]/_overview/constants";
import { formatOutcomeName } from "@/app/(app)/apis/[apiId]/_overview/utils";
import { OUTCOME_BACKGROUND_COLORS } from "@/app/(app)/[workspace]/apis/[apiId]/_overview/constants";
import { formatOutcomeName } from "@/app/(app)/[workspace]/apis/[apiId]/_overview/utils";
import { KEY_VERIFICATION_OUTCOMES } from "@unkey/clickhouse/src/keys/keys";

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { getOutcomeColor, getOutcomeOptions } from "@/app/(app)/apis/[apiId]/_overview/utils";
import {
getOutcomeColor,
getOutcomeOptions,
} from "@/app/(app)/[workspace]/apis/[apiId]/_overview/utils";
import { FilterCheckbox } from "@/components/logs/checkbox/filter-checkbox";
import { useFilters } from "../../../../hooks/use-filters";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StatusBadge } from "@/app/(app)/apis/[apiId]/settings/components/status-badge";
import { StatusBadge } from "@/app/(app)/[workspace]/apis/[apiId]/settings/components/status-badge";
import {
ControlsContainer,
ControlsLeft,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import { ResizablePanel } from "@/components/logs/details/resizable-panel";
import { useEffect, useMemo, useState } from "react";

import { LogFooter } from "@/app/(app)/logs/components/table/log-details/components/log-footer";
import { LogHeader } from "@/app/(app)/logs/components/table/log-details/components/log-header";
import { LogSection } from "@/app/(app)/logs/components/table/log-details/components/log-section";
import { DEFAULT_DRAGGABLE_WIDTH } from "@/app/(app)/logs/constants";
import { safeParseJson } from "@/app/(app)/logs/utils";
import { LogFooter } from "@/app/(app)/[workspace]/logs/components/table/log-details/components/log-footer";
import { LogHeader } from "@/app/(app)/[workspace]/logs/components/table/log-details/components/log-header";
import { LogSection } from "@/app/(app)/[workspace]/logs/components/table/log-details/components/log-section";
import { DEFAULT_DRAGGABLE_WIDTH } from "@/app/(app)/[workspace]/logs/constants";
import { safeParseJson } from "@/app/(app)/[workspace]/logs/utils";
import type { KeyDetailsLog } from "@unkey/clickhouse/src/verifications";
import { toast } from "@unkey/ui";
import { useFetchRequestDetails } from "./components/hooks/use-logs-query";
Expand Down
Loading
Loading