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"}
+
+ )}
-
+
+
+ )
+ })}
+
+ )}
-
+
{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.
+
+
+
+
+
-
+
-
-
+ {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 (
-
+