improvement(credentials): ui (#3322)

* improvement(credentials): ui

* fix: credentials logic

* improvement(credentials): ui

* improvement(credentials): members UI

* improvement(secrets): ui

* fix(credentials): show error when OAuth deletion fails due to missing fields

- Add deleteError state to track and display deletion errors
- Keep confirmation dialog open when deletion fails
- Show user-friendly error message when accountId or providerId is missing
- Add loading state to delete button during deletion
- Display error message in confirmation dialog with proper styling

Co-authored-by: Emir Karabeg <emir-karabeg@users.noreply.github.com>

* ran lint

* removed worktree file

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Emir Karabeg <emir-karabeg@users.noreply.github.com>
Co-authored-by: Waleed Latif <walif6@gmail.com>
This commit is contained in:
Emir Karabeg
2026-02-24 14:48:13 -08:00
committed by GitHub
co-authored by Emir Karabeg Cursor Agent Waleed Latif
parent 60f9eb21bf
commit 8f9b859a53
6 changed files with 874 additions and 659 deletions
@@ -142,7 +142,7 @@ export function ApiKeys({ onOpenChange }: ApiKeysProps) {
strokeWidth={2}
/>
<Input
placeholder='Search API keys...'
placeholder='Search Sim keys...'
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0'
@@ -195,7 +195,7 @@ export function ApiKeys({ onOpenChange }: ApiKeysProps) {
</div>
{workspaceKeys.length === 0 ? (
<div className='text-[13px] text-[var(--text-muted)]'>
No workspace API keys yet
No workspace Sim keys yet
</div>
) : (
workspaceKeys.map((key) => (
@@ -301,7 +301,7 @@ export function ApiKeys({ onOpenChange }: ApiKeysProps) {
</div>
{isConflict && (
<div className='text-[12px] text-[var(--text-error)] leading-tight'>
Workspace API key with the same name overrides this. Rename your
Workspace Sim key with the same name overrides this. Rename your
personal key to use it.
</div>
)}
@@ -317,7 +317,7 @@ export function ApiKeys({ onOpenChange }: ApiKeysProps) {
filteredWorkspaceKeys.length === 0 &&
(personalKeys.length > 0 || workspaceKeys.length > 0) && (
<div className='py-[16px] text-center text-[13px] text-[var(--text-muted)]'>
No API keys found matching "{searchTerm}"
No Sim keys found matching "{searchTerm}"
</div>
)}
</>
@@ -331,7 +331,7 @@ export function ApiKeys({ onOpenChange }: ApiKeysProps) {
<div className='mt-auto flex items-center justify-between'>
<div className='flex items-center gap-[8px]'>
<span className='font-medium text-[13px] text-[var(--text-secondary)]'>
Allow personal API keys
Allow personal Sim keys
</span>
<Tooltip.Root>
<Tooltip.Trigger asChild>
@@ -383,7 +383,7 @@ export function ApiKeys({ onOpenChange }: ApiKeysProps) {
{/* Delete Confirmation Dialog */}
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<ModalContent size='sm'>
<ModalHeader>Delete API key</ModalHeader>
<ModalHeader>Delete Sim key</ModalHeader>
<ModalBody>
<p className='text-[12px] text-[var(--text-secondary)]'>
Deleting{' '}
@@ -62,8 +62,8 @@ export function CreateApiKeyModal({
if (isDuplicate) {
setCreateError(
keyType === 'workspace'
? `A workspace API key named "${trimmedName}" already exists. Please choose a different name.`
: `A personal API key named "${trimmedName}" already exists. Please choose a different name.`
? `A workspace Sim key named "${trimmedName}" already exists. Please choose a different name.`
: `A personal Sim key named "${trimmedName}" already exists. Please choose a different name.`
)
return
}
@@ -86,11 +86,11 @@ export function CreateApiKeyModal({
} catch (error: unknown) {
logger.error('API key creation failed:', { error })
const errorMessage =
error instanceof Error ? error.message : 'Failed to create API key. Please try again.'
error instanceof Error ? error.message : 'Failed to create Sim key. Please try again.'
if (errorMessage.toLowerCase().includes('already exists')) {
setCreateError(errorMessage)
} else {
setCreateError('Failed to create API key. Please check your connection and try again.')
setCreateError('Failed to create Sim key. Please check your connection and try again.')
}
}
}
@@ -113,7 +113,7 @@ export function CreateApiKeyModal({
{/* Create API Key Dialog */}
<Modal open={open} onOpenChange={onOpenChange}>
<ModalContent size='sm'>
<ModalHeader>Create new API key</ModalHeader>
<ModalHeader>Create new Sim key</ModalHeader>
<ModalBody>
<p className='text-[12px] text-[var(--text-secondary)]'>
{keyType === 'workspace'
@@ -125,7 +125,7 @@ export function CreateApiKeyModal({
{canManageWorkspaceKeys && (
<div className='flex flex-col gap-[8px]'>
<p className='font-medium text-[13px] text-[var(--text-secondary)]'>
API Key Type
Sim Key Type
</p>
<ButtonGroup
value={keyType}
@@ -143,7 +143,7 @@ export function CreateApiKeyModal({
)}
<div className='flex flex-col gap-[8px]'>
<p className='font-medium text-[13px] text-[var(--text-secondary)]'>
Enter a name for your API key to help you identify it later.
Enter a name for your Sim key to help you identify it later.
</p>
{/* Hidden decoy fields to prevent browser autofill */}
<input
@@ -216,10 +216,10 @@ export function CreateApiKeyModal({
}}
>
<ModalContent size='sm'>
<ModalHeader>Your API key has been created</ModalHeader>
<ModalHeader>Your Sim key has been created</ModalHeader>
<ModalBody>
<p className='text-[12px] text-[var(--text-secondary)]'>
This is the only time you will see your API key.{' '}
This is the only time you will see your Sim key.{' '}
<span className='font-semibold text-[var(--text-primary)]'>
Copy it now and store it securely.
</span>
@@ -19,7 +19,6 @@ import {
} from 'lucide-react'
import {
Card,
Connections,
HexSimple,
Key,
SModal,
@@ -32,6 +31,7 @@ import {
SModalSidebarItem,
SModalSidebarSection,
SModalSidebarSectionTitle,
TerminalWindow,
} from '@/components/emcn'
import { AgentSkillsIcon, McpIcon } from '@/components/icons'
import { useSession } from '@/lib/auth/auth-client'
@@ -153,11 +153,11 @@ const allNavigationItems: NavigationItem[] = [
requiresHosted: true,
requiresTeam: true,
},
{ id: 'credentials', label: 'Credentials', icon: Connections, section: 'account' },
{ id: 'credentials', label: 'Secrets', icon: Key, section: 'account' },
{ id: 'custom-tools', label: 'Custom Tools', icon: Wrench, section: 'tools' },
{ id: 'skills', label: 'Skills', icon: AgentSkillsIcon, section: 'tools' },
{ id: 'mcp', label: 'MCP Tools', icon: McpIcon, section: 'tools' },
{ id: 'apikeys', label: 'API Keys', icon: Key, section: 'system' },
{ id: 'apikeys', label: 'Sim Keys', icon: TerminalWindow, section: 'system' },
{ id: 'workflow-mcp-servers', label: 'MCP Servers', icon: Server, section: 'system' },
{
id: 'byok',
@@ -461,7 +461,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
</VisuallyHidden.Root>
<VisuallyHidden.Root>
<DialogPrimitive.Description>
Configure your workspace settings, credentials, and preferences
Configure your workspace settings, secrets, and preferences
</DialogPrimitive.Description>
</VisuallyHidden.Root>
+1
View File
@@ -24,6 +24,7 @@ export { PanelLeft } from './panel-left'
export { Play, PlayOutline } from './play'
export { Redo } from './redo'
export { Rocket } from './rocket'
export { TerminalWindow } from './terminal-window'
export { Trash } from './trash'
export { Trash2 } from './trash2'
export { Undo } from './undo'
@@ -0,0 +1,26 @@
import type { SVGProps } from 'react'
/**
* Terminal window icon component
* @param props - SVG properties including className, fill, etc.
*/
export function TerminalWindow(props: SVGProps<SVGSVGElement>) {
return (
<svg
width='16'
height='14'
viewBox='0 0 16 14'
fill='none'
xmlns='http://www.w3.org/2000/svg'
{...props}
>
<path
d='M3 0C1.34315 0 0 1.34315 0 3V11C0 12.6569 1.34315 14 3 14H13C14.6569 14 16 12.6569 16 11V3C16 1.34315 14.6569 0 13 0H3ZM1 3C1 1.89543 1.89543 1 3 1H13C14.1046 1 15 1.89543 15 3V4H1V3ZM1 5H15V11C15 12.1046 14.1046 13 13 13H3C1.89543 13 1 12.1046 1 11V5Z'
fill='currentColor'
/>
<circle cx='3.5' cy='2.5' r='0.75' fill='currentColor' />
<circle cx='5.75' cy='2.5' r='0.75' fill='currentColor' />
<circle cx='8' cy='2.5' r='0.75' fill='currentColor' />
</svg>
)
}