22
33import { createElement , useEffect , useMemo , useState } from 'react'
44import { createLogger } from '@sim/logger'
5- import { Plus , Search , Share2 , Trash2 } from 'lucide-react'
5+ import { AlertTriangle , Plus , Search , Share2 , Trash2 } from 'lucide-react'
66import { useParams } from 'next/navigation'
77import {
88 Badge ,
@@ -297,7 +297,7 @@ export function CredentialsManager() {
297297 [ createSecretScope ]
298298 )
299299 const selectedExistingEnvCredential = useMemo ( ( ) => {
300- if ( createType !== 'secret' ) return null
300+ if ( createType !== 'secret' || createSecretInputMode !== 'single' ) return null
301301 const envKey = normalizeEnvKeyInput ( createEnvKey )
302302 if ( ! envKey ) return null
303303 return (
@@ -306,7 +306,31 @@ export function CredentialsManager() {
306306 row . type === createSecretType && ( row . envKey || '' ) . toLowerCase ( ) === envKey . toLowerCase ( )
307307 ) ?? null
308308 )
309- } , [ credentials , createEnvKey , createSecretType , createType ] )
309+ } , [ credentials , createEnvKey , createSecretType , createType , createSecretInputMode ] )
310+
311+ const crossScopeEnvConflict = useMemo ( ( ) => {
312+ if ( createType !== 'secret' || createSecretInputMode !== 'single' ) return null
313+ if ( createSecretScope !== 'personal' ) return null
314+ const envKey = normalizeEnvKeyInput ( createEnvKey )
315+ if ( ! envKey ) return null
316+ return (
317+ credentials . find (
318+ ( row ) =>
319+ row . type === 'env_workspace' && ( row . envKey || '' ) . toLowerCase ( ) === envKey . toLowerCase ( )
320+ ) ?? null
321+ )
322+ } , [ credentials , createEnvKey , createSecretScope , createType , createSecretInputMode ] )
323+
324+ const existingOAuthDisplayName = useMemo ( ( ) => {
325+ if ( createType !== 'oauth' ) return null
326+ const name = createDisplayName . trim ( )
327+ if ( ! name ) return null
328+ return (
329+ credentials . find (
330+ ( row ) => row . type === 'oauth' && row . displayName . toLowerCase ( ) === name . toLowerCase ( )
331+ ) ?? null
332+ )
333+ } , [ credentials , createDisplayName , createType ] )
310334 const selectedEnvCurrentValue = useMemo ( ( ) => {
311335 if ( ! selectedCredential || selectedCredential . type === 'oauth' ) return ''
312336 const envKey = selectedCredential . envKey || ''
@@ -1309,6 +1333,20 @@ export function CredentialsManager() {
13091333 />
13101334 </ div >
13111335 </ div >
1336+ { existingOAuthDisplayName && (
1337+ < div className = 'rounded-[8px] border border-red-500/50 bg-red-50 p-[12px] dark:bg-red-950/30' >
1338+ < div className = 'flex items-start gap-[10px]' >
1339+ < AlertTriangle className = 'mt-[1px] h-4 w-4 flex-shrink-0 text-red-600 dark:text-red-400' />
1340+ < p className = 'text-[12px] text-red-700 dark:text-red-300' >
1341+ A credential named{ ' ' }
1342+ < span className = 'font-medium' >
1343+ { existingOAuthDisplayName . displayName }
1344+ </ span > { ' ' }
1345+ already exists.
1346+ </ p >
1347+ </ div >
1348+ </ div >
1349+ ) }
13121350 </ div >
13131351 ) : (
13141352 < div className = 'flex flex-col gap-[10px]' >
@@ -1411,28 +1449,29 @@ export function CredentialsManager() {
14111449 </ div >
14121450
14131451 { selectedExistingEnvCredential && (
1414- < div className = 'rounded-[8px] border border-[var(--brand-9)]/40 bg-[var(--surface-3)] px-[10px] py-[8px]' >
1415- < p className = 'text-[12px] text-[var(--text-primary)]' >
1416- This secret key already maps to credential{ ' ' }
1417- < span className = 'font-medium' >
1418- { selectedExistingEnvCredential . displayName }
1419- </ span >
1420- .
1421- </ p >
1422- < p className = 'mt-[4px] text-[11px] text-[var(--text-tertiary)]' >
1423- Create will update the secret value and reuse the existing credential.
1424- </ p >
1425- < Button
1426- variant = 'ghost'
1427- className = 'mt-[6px]'
1428- onClick = { ( ) => {
1429- setSelectedCredentialId ( selectedExistingEnvCredential . id )
1430- setShowCreateModal ( false )
1431- resetCreateForm ( )
1432- } }
1433- >
1434- Open existing credential
1435- </ Button >
1452+ < div className = 'rounded-[8px] border border-red-500/50 bg-red-50 p-[12px] dark:bg-red-950/30' >
1453+ < div className = 'flex items-start gap-[10px]' >
1454+ < AlertTriangle className = 'mt-[1px] h-4 w-4 flex-shrink-0 text-red-600 dark:text-red-400' />
1455+ < p className = 'text-[12px] text-red-700 dark:text-red-300' >
1456+ A secret with key{ ' ' }
1457+ < span className = 'font-medium' >
1458+ { selectedExistingEnvCredential . displayName }
1459+ </ span > { ' ' }
1460+ already exists.
1461+ </ p >
1462+ </ div >
1463+ </ div >
1464+ ) }
1465+ { ! selectedExistingEnvCredential && crossScopeEnvConflict && (
1466+ < div className = 'rounded-[8px] border border-amber-500/50 bg-amber-50 p-[12px] dark:bg-amber-950/30' >
1467+ < div className = 'flex items-start gap-[10px]' >
1468+ < AlertTriangle className = 'mt-[1px] h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400' />
1469+ < p className = 'text-[12px] text-amber-700 dark:text-amber-300' >
1470+ A workspace secret with key{ ' ' }
1471+ < span className = 'font-medium' > { crossScopeEnvConflict . envKey } </ span > { ' ' }
1472+ already exists. Workspace secrets take precedence at runtime.
1473+ </ p >
1474+ </ div >
14361475 </ div >
14371476 ) }
14381477 </ >
@@ -1471,8 +1510,13 @@ export function CredentialsManager() {
14711510 ) }
14721511
14731512 { createError && (
1474- < div className = 'whitespace-pre-wrap rounded-[8px] border border-[var(--status-red)]/40 bg-[var(--status-red)]/10 px-[10px] py-[8px] text-[12px] text-[var(--status-red)]' >
1475- { createError }
1513+ < div className = 'rounded-[8px] border border-red-500/50 bg-red-50 p-[12px] dark:bg-red-950/30' >
1514+ < div className = 'flex items-start gap-[10px]' >
1515+ < AlertTriangle className = 'mt-[1px] h-4 w-4 flex-shrink-0 text-red-600 dark:text-red-400' />
1516+ < p className = 'whitespace-pre-wrap text-[12px] text-red-700 dark:text-red-300' >
1517+ { createError }
1518+ </ p >
1519+ </ div >
14761520 </ div >
14771521 ) }
14781522 </ div >
@@ -1488,10 +1532,13 @@ export function CredentialsManager() {
14881532 ( createType === 'oauth'
14891533 ? ! createOAuthProviderId ||
14901534 ! createDisplayName . trim ( ) ||
1491- connectOAuthService . isPending
1535+ connectOAuthService . isPending ||
1536+ Boolean ( existingOAuthDisplayName )
14921537 : createSecretInputMode === 'bulk'
14931538 ? ! createBulkText . trim ( )
1494- : ! createEnvKey . trim ( ) || ! createEnvValue . trim ( ) ) ||
1539+ : ! createEnvKey . trim ( ) ||
1540+ ! createEnvValue . trim ( ) ||
1541+ Boolean ( selectedExistingEnvCredential ) ) ||
14951542 createCredential . isPending ||
14961543 savePersonalEnvironment . isPending ||
14971544 upsertWorkspaceEnvironment . isPending ||
@@ -1508,9 +1555,7 @@ export function CredentialsManager() {
15081555 upsertWorkspaceEnvironment . isPending
15091556 ? 'Importing...'
15101557 : 'Import all'
1511- : selectedExistingEnvCredential
1512- ? 'Update and use existing'
1513- : 'Create' }
1558+ : 'Create' }
15141559 </ Button >
15151560 </ ModalFooter >
15161561 </ ModalContent >
0 commit comments