From 72e4fd59601f9bf9a1028cc304bb70b2ec7926b9 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Tue, 24 Feb 2026 10:19:52 -0800 Subject: [PATCH 1/8] improvement(credentials): ui --- .../credentials/credentials-manager.tsx | 1276 +++++++++-------- 1 file changed, 689 insertions(+), 587 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials-manager.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials-manager.tsx index c2e6960f34..9fdb7ce5cb 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials-manager.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials-manager.tsx @@ -2,7 +2,7 @@ import { createElement, useCallback, useEffect, useMemo, useState } from 'react' import { createLogger } from '@sim/logger' -import { AlertTriangle, Check, Copy, Plus, RefreshCw, Search, Share2, Trash2 } from 'lucide-react' +import { AlertTriangle, Check, Copy, Plus, RefreshCw, Search, Share2 } from 'lucide-react' import { useParams } from 'next/navigation' import { Badge, @@ -20,9 +20,8 @@ import { Textarea, Tooltip, } from '@/components/emcn' -import { Skeleton } from '@/components/ui' +import { Skeleton, Input as UiInput } from '@/components/ui' import { useSession } from '@/lib/auth/auth-client' -import { cn } from '@/lib/core/utils/cn' import { clearPendingCredentialCreateRequest, PENDING_CREDENTIAL_CREATE_REQUEST_EVENT, @@ -151,9 +150,9 @@ function typeBadgeVariant(type: WorkspaceCredential['type']): 'blue' | 'amber' | } function typeLabel(type: WorkspaceCredential['type']): string { - if (type === 'oauth') return 'OAuth' - if (type === 'env_workspace') return 'Workspace Secret' - return 'Personal Secret' + if (type === 'oauth') return 'oauth' + if (type === 'env_workspace') return 'workspace secret' + return 'personal secret' } function normalizeEnvKeyInput(raw: string): string { @@ -162,6 +161,21 @@ function normalizeEnvKeyInput(raw: string): string { return wrappedMatch ? wrappedMatch[1] : trimmed } +function CredentialSkeleton() { + return ( +
+
+ + +
+
+ + +
+
+ ) +} + export function CredentialsManager() { const params = useParams() const workspaceId = (params?.workspaceId as string) || '' @@ -188,6 +202,8 @@ export function CredentialsManager() { const [selectedDisplayNameDraft, setSelectedDisplayNameDraft] = useState('') const [showCreateOAuthRequiredModal, setShowCreateOAuthRequiredModal] = useState(false) const [copyIdSuccess, setCopyIdSuccess] = useState(false) + const [credentialToDelete, setCredentialToDelete] = useState(null) + const [showDeleteConfirmDialog, setShowDeleteConfirmDialog] = useState(false) const { data: session } = useSession() const currentUserId = session?.user?.id || '' @@ -773,6 +789,44 @@ export function CredentialsManager() { } } + const handleDeleteClick = (credential: WorkspaceCredential) => { + setCredentialToDelete(credential) + setShowDeleteConfirmDialog(true) + } + + const handleConfirmDelete = async () => { + if (!credentialToDelete) return + setShowDeleteConfirmDialog(false) + + try { + if (credentialToDelete.type === 'oauth') { + if (credentialToDelete.accountId && credentialToDelete.providerId) { + await disconnectOAuthService.mutateAsync({ + provider: credentialToDelete.providerId.split('-')[0] || credentialToDelete.providerId, + providerId: credentialToDelete.providerId, + serviceId: credentialToDelete.providerId, + accountId: credentialToDelete.accountId, + }) + await refetchCredentials() + window.dispatchEvent( + new CustomEvent('oauth-credentials-updated', { + detail: { providerId: credentialToDelete.providerId, workspaceId }, + }) + ) + } + } else { + await deleteCredential.mutateAsync(credentialToDelete.id) + } + if (selectedCredentialId === credentialToDelete.id) { + setSelectedCredentialId(null) + } + } catch (error) { + logger.error('Failed to delete credential', error) + } finally { + setCredentialToDelete(null) + } + } + const [isPromoting, setIsPromoting] = useState(false) const [isShareingWithWorkspace, setIsSharingWithWorkspace] = useState(false) @@ -964,81 +1018,359 @@ export function CredentialsManager() { } } - return ( -
-
-
-
- - setSearchTerm(event.target.value)} - placeholder='Search credentials...' - className='pl-[32px]' - /> -
- -
- -
- {credentialsLoading ? ( -
- - - -
- ) : sortedCredentials.length === 0 ? ( -
- No credentials available for this workspace. + const hasCredentials = credentials && credentials.length > 0 + const showNoResults = + searchTerm.trim() && sortedCredentials.length === 0 && credentials.length > 0 + + const createModalJsx = ( + { + setShowCreateModal(open) + if (!open) resetCreateForm() + }} + > + + Create Credential + +
+
+ +
+ ({ + value: option.value, + label: option.label, + }))} + value={ + createTypeOptions.find((option) => option.value === createType)?.label || '' + } + selectedValue={createType} + onChange={(value) => { + setCreateType(value as CreateCredentialType) + setCreateError(null) + }} + placeholder='Select credential type' + /> +
- ) : ( -
- {sortedCredentials.map((credential) => ( -