Skip to content

Commit 7953022

Browse files
committed
improvement(credentials): ui
1 parent 67f51a7 commit 7953022

File tree

1 file changed

+127
-84
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials

1 file changed

+127
-84
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials-manager.tsx

Lines changed: 127 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ export function CredentialsManager() {
277277
oauthConnections.map((service) => ({
278278
value: service.providerId,
279279
label: service.name,
280+
icon: getServiceConfigByProviderId(service.providerId)?.icon,
280281
})),
281282
[oauthConnections]
282283
)
@@ -786,20 +787,22 @@ export function CredentialsManager() {
786787

787788
try {
788789
if (credentialToDelete.type === 'oauth') {
789-
if (credentialToDelete.accountId && credentialToDelete.providerId) {
790-
await disconnectOAuthService.mutateAsync({
791-
provider: credentialToDelete.providerId.split('-')[0] || credentialToDelete.providerId,
792-
providerId: credentialToDelete.providerId,
793-
serviceId: credentialToDelete.providerId,
794-
accountId: credentialToDelete.accountId,
795-
})
796-
await refetchCredentials()
797-
window.dispatchEvent(
798-
new CustomEvent('oauth-credentials-updated', {
799-
detail: { providerId: credentialToDelete.providerId, workspaceId },
800-
})
801-
)
790+
if (!credentialToDelete.accountId || !credentialToDelete.providerId) {
791+
logger.error('Cannot disconnect OAuth credential: missing accountId or providerId')
792+
return
802793
}
794+
await disconnectOAuthService.mutateAsync({
795+
provider: credentialToDelete.providerId.split('-')[0] || credentialToDelete.providerId,
796+
providerId: credentialToDelete.providerId,
797+
serviceId: credentialToDelete.providerId,
798+
accountId: credentialToDelete.accountId,
799+
})
800+
await refetchCredentials()
801+
window.dispatchEvent(
802+
new CustomEvent('oauth-credentials-updated', {
803+
detail: { providerId: credentialToDelete.providerId, workspaceId },
804+
})
805+
)
803806
} else {
804807
await deleteCredential.mutateAsync(credentialToDelete.id)
805808
}
@@ -1016,10 +1019,33 @@ export function CredentialsManager() {
10161019
</div>
10171020
</div>
10181021

1022+
{createType === 'secret' && (
1023+
<div>
1024+
<Label>Mode</Label>
1025+
<div className='mt-[6px]'>
1026+
<Combobox
1027+
options={[
1028+
{ value: 'single', label: 'Single' },
1029+
{ value: 'bulk', label: 'Bulk' },
1030+
]}
1031+
value={createSecretInputMode === 'single' ? 'Single' : 'Bulk'}
1032+
selectedValue={createSecretInputMode}
1033+
onChange={(value) => {
1034+
setCreateSecretInputMode(value as SecretInputMode)
1035+
setCreateError(null)
1036+
}}
1037+
placeholder='Select mode'
1038+
/>
1039+
</div>
1040+
</div>
1041+
)}
1042+
10191043
{createType === 'oauth' ? (
10201044
<div className='flex flex-col gap-[10px]'>
10211045
<div>
1022-
<Label>Display name</Label>
1046+
<Label>
1047+
Display name<span className='ml-1'>*</span>
1048+
</Label>
10231049
<Input
10241050
value={createDisplayName}
10251051
onChange={(event) => setCreateDisplayName(event.target.value)}
@@ -1040,7 +1066,7 @@ export function CredentialsManager() {
10401066
/>
10411067
</div>
10421068
<div>
1043-
<Label>OAuth service</Label>
1069+
<Label>Account</Label>
10441070
<div className='mt-[6px]'>
10451071
<Combobox
10461072
options={oauthServiceOptions}
@@ -1051,6 +1077,27 @@ export function CredentialsManager() {
10511077
selectedValue={createOAuthProviderId}
10521078
onChange={setCreateOAuthProviderId}
10531079
placeholder='Select OAuth service'
1080+
searchable
1081+
searchPlaceholder='Search services...'
1082+
overlayContent={
1083+
createOAuthProviderId
1084+
? (() => {
1085+
const config = getServiceConfigByProviderId(createOAuthProviderId)
1086+
const label =
1087+
oauthServiceOptions.find((o) => o.value === createOAuthProviderId)
1088+
?.label || ''
1089+
return (
1090+
<div className='flex items-center gap-[8px]'>
1091+
{config &&
1092+
createElement(config.icon, {
1093+
className: 'h-[14px] w-[14px] flex-shrink-0',
1094+
})}
1095+
<span className='truncate'>{label}</span>
1096+
</div>
1097+
)
1098+
})()
1099+
: undefined
1100+
}
10541101
/>
10551102
</div>
10561103
</div>
@@ -1071,56 +1118,36 @@ export function CredentialsManager() {
10711118
<div className='flex flex-col gap-[10px]'>
10721119
<div>
10731120
<Label className='block'>Scope</Label>
1074-
<div className='mt-[6px]'>
1075-
<ButtonGroup
1076-
value={createSecretScope}
1077-
onValueChange={(value) => setCreateSecretScope(value as SecretScope)}
1121+
<ButtonGroup
1122+
value={createSecretScope}
1123+
onValueChange={(value) => setCreateSecretScope(value as SecretScope)}
1124+
className='mt-[6px]'
1125+
>
1126+
<ButtonGroupItem
1127+
value='workspace'
1128+
className='h-[28px] min-w-[80px] px-[10px] py-0 text-[12px]'
10781129
>
1079-
<ButtonGroupItem
1080-
value='workspace'
1081-
className='h-[28px] min-w-[80px] px-[10px] py-0 text-[12px]'
1082-
>
1083-
Workspace
1084-
</ButtonGroupItem>
1085-
<ButtonGroupItem
1086-
value='personal'
1087-
className='h-[28px] min-w-[72px] px-[10px] py-0 text-[12px]'
1088-
>
1089-
Personal
1090-
</ButtonGroupItem>
1091-
</ButtonGroup>
1092-
</div>
1093-
</div>
1094-
<div>
1095-
<Label className='block'>Mode</Label>
1096-
<div className='mt-[6px]'>
1097-
<ButtonGroup
1098-
value={createSecretInputMode}
1099-
onValueChange={(value) => {
1100-
setCreateSecretInputMode(value as SecretInputMode)
1101-
setCreateError(null)
1102-
}}
1130+
Workspace
1131+
</ButtonGroupItem>
1132+
<ButtonGroupItem
1133+
value='personal'
1134+
className='h-[28px] min-w-[72px] px-[10px] py-0 text-[12px]'
11031135
>
1104-
<ButtonGroupItem
1105-
value='single'
1106-
className='h-[28px] min-w-[56px] px-[10px] py-0 text-[12px]'
1107-
>
1108-
Single
1109-
</ButtonGroupItem>
1110-
<ButtonGroupItem
1111-
value='bulk'
1112-
className='h-[28px] min-w-[56px] px-[10px] py-0 text-[12px]'
1113-
>
1114-
Bulk
1115-
</ButtonGroupItem>
1116-
</ButtonGroup>
1117-
</div>
1136+
Personal
1137+
</ButtonGroupItem>
1138+
</ButtonGroup>
1139+
<p className='mt-[4px] text-[11px] text-[var(--text-tertiary)]'>
1140+
{createSecretScope === 'workspace'
1141+
? 'Shared across the workspace. All members can use it.'
1142+
: 'Only visible to you. Other members cannot use it.'}
1143+
</p>
11181144
</div>
1119-
11201145
{createSecretInputMode === 'single' ? (
11211146
<>
11221147
<div>
1123-
<Label>Secret key</Label>
1148+
<Label>
1149+
Secret key<span className='ml-1'>*</span>
1150+
</Label>
11241151
<Input
11251152
value={createEnvKey}
11261153
onChange={(event) => {
@@ -1140,7 +1167,9 @@ export function CredentialsManager() {
11401167
</p>
11411168
</div>
11421169
<div>
1143-
<Label>Secret value</Label>
1170+
<Label>
1171+
Secret value<span className='ml-1'>*</span>
1172+
</Label>
11441173
<Input
11451174
type='password'
11461175
value={createEnvValue}
@@ -1713,32 +1742,46 @@ export function CredentialsManager() {
17131742
</div>
17141743
) : (
17151744
<div className='flex flex-col gap-[8px]'>
1716-
{sortedCredentials.map((credential) => (
1717-
<div key={credential.id} className='flex items-center justify-between gap-[12px]'>
1718-
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
1719-
<span className='truncate font-medium text-[14px]'>
1720-
{credential.displayName}
1721-
</span>
1722-
<p className='truncate text-[13px] text-[var(--text-muted)]'>
1723-
{credential.description || 'No description'}
1724-
</p>
1725-
</div>
1726-
<div className='flex flex-shrink-0 items-center gap-[4px]'>
1727-
<Button variant='default' onClick={() => handleSelectCredential(credential)}>
1728-
Details
1729-
</Button>
1730-
{credential.role === 'admin' && (
1731-
<Button
1732-
variant='ghost'
1733-
onClick={() => handleDeleteClick(credential)}
1734-
disabled={deleteCredential.isPending || disconnectOAuthService.isPending}
1735-
>
1736-
Delete
1745+
{sortedCredentials.map((credential) => {
1746+
const serviceConfig =
1747+
credential.type === 'oauth' && credential.providerId
1748+
? getServiceConfigByProviderId(credential.providerId)
1749+
: null
1750+
1751+
return (
1752+
<div key={credential.id} className='flex items-center justify-between gap-[12px]'>
1753+
<div className='flex min-w-0 items-center gap-[10px]'>
1754+
{serviceConfig && (
1755+
<div className='flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-[6px] bg-[var(--surface-5)]'>
1756+
{createElement(serviceConfig.icon, { className: 'h-4 w-4' })}
1757+
</div>
1758+
)}
1759+
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
1760+
<span className='truncate font-medium text-[14px]'>
1761+
{credential.displayName}
1762+
</span>
1763+
<p className='truncate text-[13px] text-[var(--text-muted)]'>
1764+
{credential.description || 'No description'}
1765+
</p>
1766+
</div>
1767+
</div>
1768+
<div className='flex flex-shrink-0 items-center gap-[4px]'>
1769+
<Button variant='default' onClick={() => handleSelectCredential(credential)}>
1770+
Details
17371771
</Button>
1738-
)}
1772+
{credential.role === 'admin' && (
1773+
<Button
1774+
variant='ghost'
1775+
onClick={() => handleDeleteClick(credential)}
1776+
disabled={deleteCredential.isPending || disconnectOAuthService.isPending}
1777+
>
1778+
Delete
1779+
</Button>
1780+
)}
1781+
</div>
17391782
</div>
1740-
</div>
1741-
))}
1783+
)
1784+
})}
17421785
{showNoResults && (
17431786
<div className='py-[16px] text-center text-[13px] text-[var(--text-muted)]'>
17441787
No credentials found matching &ldquo;{searchTerm}&rdquo;

0 commit comments

Comments
 (0)