From 5c336956f43b0853b2b976c1e61e1940ca24f3bf Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommu Date: Fri, 7 Aug 2026 11:23:35 -0700 Subject: [PATCH] fix(web): align API keys settings UI with design system - house modal style + PillButton primaries for create/reveal/revoke dialogs - portal dialogs into settings modal so inputs stay clickable - per-key inset card list with compact meta line and quiet revoke icon --- apps/web/components/settings/api-keys.tsx | 447 +++++++++--------- .../components/settings/settings-content.tsx | 4 +- packages/ui/components/alert-dialog.tsx | 7 +- 3 files changed, 230 insertions(+), 228 deletions(-) diff --git a/apps/web/components/settings/api-keys.tsx b/apps/web/components/settings/api-keys.tsx index 3c591372..30d7f0af 100644 --- a/apps/web/components/settings/api-keys.tsx +++ b/apps/web/components/settings/api-keys.tsx @@ -18,12 +18,9 @@ import { import { Dialog, DialogContent, - DialogDescription, - DialogFooter, DialogHeader, DialogTitle, } from "@ui/components/dialog" -import { Input } from "@ui/components/input" import { Select, SelectContent, @@ -31,8 +28,18 @@ import { SelectTrigger, SelectValue, } from "@ui/components/select" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { PillButton } from "../integrations/install-steps" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import { Check, Copy, KeyRound, Loader2, Plus, Trash2 } from "lucide-react" +import { + Check, + Copy, + KeyRound, + Loader2, + Plus, + Trash2, + XIcon, +} from "lucide-react" import { useCallback, useId, useState } from "react" import { toast } from "sonner" @@ -62,6 +69,27 @@ const EXPIRY_OPTIONS = [ const API_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" +const MODAL_SHADOW = + "0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset" + +const pillInputClass = + "h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" + +function ModalClose() { + return ( + + + Close + + ) +} + function SettingsCard({ children }: { children: React.ReactNode }) { return (
- + - - {isLoading ? ( -
+ {isLoading ? ( + +
Loading keys…
- ) : isError ? ( +
+ ) : isError ? ( +

Couldn't load API keys. @@ -299,7 +322,9 @@ export default function ApiKeys() { Try again

- ) : keys.length === 0 ? ( +
+ ) : keys.length === 0 ? ( +
@@ -316,115 +341,116 @@ export default function ApiKeys() {

Create your first key to use the API programmatically or connect custom tools.

- +
+ { + setCreatedKey(null) + setCreateOpen(true) + }} + > + + Create key + +
- ) : ( -
    - {keys.map((key) => { - const expired = isExpired(key.expiresAt) - const disabled = key.enabled === false || expired - return ( -
  • + ) : ( +
      + {keys.map((key) => { + const expired = isExpired(key.expiresAt) + const disabled = key.enabled === false || expired + return ( +
    • +
      -
      -
      -

      +

      +
      +
      +

      + {key.name?.trim() || "Unnamed key"} +

      + {key.isScoped && ( + - {key.name?.trim() || "Unnamed key"} -

      - {key.isScoped && ( - - Scoped - - )} - {disabled && ( - - {expired ? "Expired" : "Disabled"} - - )} -
      - - {formatKeyPreview(key.start ?? key.key)} - -
      - Created {formatRelativeTime(key.createdAt)} - - Last used{" "} - {key.lastRequest - ? formatRelativeTime(key.lastRequest) - : "never"} + Scoped - Expires {formatExpiresLabel(key.expiresAt)} -
      + )} + {disabled && ( + + {expired ? "Expired" : "Disabled"} + + )}
      - -
    • - ) - })} -
    - )} - + + {formatKeyPreview(key.start ?? key.key)} + + · + Created {formatRelativeTime(key.createdAt)} + · + + Last used{" "} + {key.lastRequest + ? formatRelativeTime(key.lastRequest) + : "never"} + + · + Expires {formatExpiresLabel(key.expiresAt)} +
+ + + + ) + })} + + )}

- + {createdKey ? ( <> - - - API key created - - - Copy this key now. You won't be able to see it again. - - -

-
- - {createdKey} - - -
-

+

+ + + API key created + +

+ Copy this key now. You won't be able to see it again. +

+
+ +
+
+ + {createdKey} + +

Store it somewhere safe. For security, the full key is only shown once.

- +
- + handleCopy(createdKey)}> + {copied ? ( + + ) : ( + + )} + {copied ? "Copied" : "Copy key"} + +
) : ( <> - - - Create API key - - - This key has full access to your organization's - Supermemory data via the API. - - -
+
+ + + Create API key + +

+ This key has full access to your organization's + Supermemory data via the API. +

+
+ +
+
- setKeyName(e.target.value)} placeholder="e.g. production, local-dev" - className="h-10 border-white/[0.08] bg-[#0D121A] text-[#FAFAFA] placeholder:text-[#6B6B6B]" + className={pillInputClass} autoComplete="off" onKeyDown={(e) => { if (e.key === "Enter" && !createKeyMutation.isPending) { @@ -558,19 +567,16 @@ export default function ApiKeys() { />
- + Expires
- +
- - + {createKeyMutation.isPending ? "Creating…" : "Create"} + +
)} @@ -627,12 +617,19 @@ export default function ApiKeys() { if (!open && !revokeKeyMutation.isPending) setRevokeTarget(null) }} > - - - + + + Revoke API key? - + {revokeTarget?.name?.trim() ? `"${revokeTarget.name}" will stop working immediately.` : "This key will stop working immediately."}{" "} @@ -640,10 +637,10 @@ export default function ApiKeys() { undone. - + Cancel @@ -653,7 +650,7 @@ export default function ApiKeys() { e.preventDefault() if (revokeTarget) revokeKeyMutation.mutate(revokeTarget.id) }} - className="rounded-full bg-[#C73B1B] text-white hover:bg-[#A83217]" + className="h-9 rounded-full bg-[#C73B1B] px-4 text-[13px] font-semibold text-white hover:bg-[#A83217]" > {revokeKeyMutation.isPending ? ( diff --git a/apps/web/components/settings/settings-content.tsx b/apps/web/components/settings/settings-content.tsx index 9b03bddd..d5147ec7 100644 --- a/apps/web/components/settings/settings-content.tsx +++ b/apps/web/components/settings/settings-content.tsx @@ -490,7 +490,9 @@ export function SettingsContent({ )} {activeTab === "billing" && } - {activeTab === "api-keys" && } + {activeTab === "api-keys" && ( + + )} {activeTab === "integrations" && } {activeTab === "connections" && } {activeTab === "support" && } diff --git a/packages/ui/components/alert-dialog.tsx b/packages/ui/components/alert-dialog.tsx index 619f8516..e97d8ed1 100644 --- a/packages/ui/components/alert-dialog.tsx +++ b/packages/ui/components/alert-dialog.tsx @@ -45,10 +45,13 @@ function AlertDialogOverlay({ function AlertDialogContent({ className, + portalContainer, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + portalContainer?: HTMLElement | null +}) { return ( - +