11'use client'
22
33import { useCallback , useMemo , useState } from 'react'
4- import { Button , Combobox } from '@sim/emcn'
4+ import { Button , Combobox , type ComboboxOptionGroup } from '@sim/emcn'
55import { ExternalLink , KeyRound } from 'lucide-react'
66import { useParams } from 'next/navigation'
77import { consumeOAuthReturnContext , writeOAuthReturnContext } from '@/lib/credentials/client-state'
@@ -18,7 +18,6 @@ import {
1818 ConnectServiceAccountModal ,
1919 type ServiceAccountProviderId ,
2020} from '@/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal'
21- import { ConnectSlackBotModal } from '@/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal'
2221import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
2322import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight'
2423import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
@@ -53,8 +52,7 @@ export function CredentialSelector({
5352 const workspaceId = ( params ?. workspaceId as string ) || ''
5453 const [ showConnectModal , setShowConnectModal ] = useState ( false )
5554 const [ showOAuthModal , setShowOAuthModal ] = useState ( false )
56- const [ showSlackBotModal , setShowSlackBotModal ] = useState ( false )
57- const [ showServiceAccountModal , setShowServiceAccountModal ] = useState ( false )
55+ const [ showSetupModal , setShowSetupModal ] = useState ( false )
5856 const [ editingValue , setEditingValue ] = useState ( '' )
5957 const [ isEditing , setIsEditing ] = useState ( false )
6058 const activeWorkflowId = useWorkflowRegistry ( ( state ) => state . activeWorkflowId )
@@ -104,24 +102,32 @@ export function CredentialSelector({
104102 const credentialsLoading = isAllCredentials ? allCredentialsLoading : oauthCredentialsLoading
105103
106104 const credentialKind = subBlock . credentialKind
105+ const isMergedKinds = credentialKind === 'any'
107106
108107 const credentials = useMemo ( ( ) => {
109- // A custom-bot or service-account picker lists only the reusable
110- // service-account credentials, including in trigger mode.
111- if ( credentialKind === 'custom-bot' || credentialKind === 'service-account' ) {
108+ // A service-account picker lists only the reusable service-account
109+ // credentials, including in trigger mode. A merged ('any') picker lists
110+ // OAuth accounts and service accounts together.
111+ if ( credentialKind === 'service-account' ) {
112112 return rawCredentials . filter ( ( cred ) => cred . type === 'service_account' )
113113 }
114+ if ( isMergedKinds ) {
115+ return rawCredentials
116+ }
114117 return isTriggerMode && ! subBlock . allowServiceAccounts
115118 ? rawCredentials . filter ( ( cred ) => cred . type !== 'service_account' )
116119 : rawCredentials
117- } , [ rawCredentials , isTriggerMode , credentialKind , subBlock . allowServiceAccounts ] )
120+ } , [ rawCredentials , isTriggerMode , credentialKind , isMergedKinds , subBlock . allowServiceAccounts ] )
118121
119- // Resolved service-account provider metadata for the token-paste connect
120- // modal. Gated on `credentialKind` and using the non-throwing lookup so it
121- // never runs (or throws) for the OAuth / custom-bot pickers.
122+ // Resolved service-account provider metadata for the setup modal. Gated on
123+ // `credentialKind` and using the non-throwing lookup so it never runs (or
124+ // throws) for plain OAuth pickers.
122125 const serviceAccountService = useMemo (
123- ( ) => ( credentialKind === 'service-account' ? getServiceConfigByServiceId ( serviceId ) : null ) ,
124- [ credentialKind , serviceId ]
126+ ( ) =>
127+ credentialKind === 'service-account' || isMergedKinds
128+ ? getServiceConfigByServiceId ( serviceId )
129+ : null ,
130+ [ credentialKind , isMergedKinds , serviceId ]
125131 )
126132
127133 const selectedCredential = useMemo (
@@ -198,12 +204,8 @@ export function CredentialSelector({
198204 )
199205
200206 const handleAddCredential = useCallback ( ( ) => {
201- if ( credentialKind === 'custom-bot' ) {
202- setShowSlackBotModal ( true )
203- return
204- }
205207 if ( credentialKind === 'service-account' ) {
206- setShowServiceAccountModal ( true )
208+ setShowSetupModal ( true )
207209 return
208210 }
209211 setShowConnectModal ( true )
@@ -239,6 +241,7 @@ export function CredentialSelector({
239241 const oauthCredentials = allWorkspaceCredentials . filter ( ( c ) => c . type === 'oauth' )
240242 return oauthCredentials . map ( ( cred ) => ( { label : cred . displayName , value : cred . id } ) )
241243 }
244+ if ( isMergedKinds ) return [ ]
242245
243246 const options = credentials . map ( ( cred ) => ( {
244247 label : cred . name ,
@@ -248,27 +251,69 @@ export function CredentialSelector({
248251
249252 options . push ( {
250253 label :
251- credentialKind === 'custom-bot'
252- ? credentials . length > 0
253- ? 'Connect another custom bot'
254- : 'Set up a custom bot'
255- : credentialKind === 'service-account'
256- ? credentials . length > 0
254+ credentialKind === 'service-account'
255+ ? ( subBlock . credentialLabels ?. serviceAccountConnect ??
256+ ( credentials . length > 0
257257 ? `Add another ${ getProviderName ( provider ) } key`
258- : `Add ${ getProviderName ( provider ) } key`
259- : credentials . length > 0
260- ? `Connect another ${ getProviderName ( provider ) } account`
261- : `Connect ${ getProviderName ( provider ) } account` ,
258+ : `Add ${ getProviderName ( provider ) } key` ) )
259+ : credentials . length > 0
260+ ? `Connect another ${ getProviderName ( provider ) } account`
261+ : `Connect ${ getProviderName ( provider ) } account` ,
262262 value : '__connect_account__' ,
263263 iconElement : < ExternalLink className = 'size-3' /> ,
264264 } )
265265
266266 return options
267267 } , [
268268 isAllCredentials ,
269+ isMergedKinds ,
269270 allWorkspaceCredentials ,
270271 credentials ,
271272 credentialKind ,
273+ subBlock . credentialLabels ,
274+ provider ,
275+ getProviderIcon ,
276+ getProviderName ,
277+ ] )
278+
279+ const comboboxGroups = useMemo < ComboboxOptionGroup [ ] | undefined > ( ( ) => {
280+ if ( ! isMergedKinds ) return undefined
281+
282+ const labels = subBlock . credentialLabels
283+ const toOption = ( cred : ( typeof credentials ) [ number ] ) => ( {
284+ label : cred . name ,
285+ value : cred . id ,
286+ iconElement : getProviderIcon ( ( cred . provider ?? provider ) as OAuthProvider ) ,
287+ } )
288+
289+ return [
290+ {
291+ section : labels ?. oauthGroup ?? `${ getProviderName ( provider ) } accounts` ,
292+ items : [
293+ ...credentials . filter ( ( c ) => c . type !== 'service_account' ) . map ( toOption ) ,
294+ {
295+ label : labels ?. oauthConnect ?? `Connect ${ getProviderName ( provider ) } account` ,
296+ value : '__connect_account__' ,
297+ iconElement : < ExternalLink className = 'size-3' /> ,
298+ } ,
299+ ] ,
300+ } ,
301+ {
302+ section : labels ?. serviceAccountGroup ?? 'Service accounts' ,
303+ items : [
304+ ...credentials . filter ( ( c ) => c . type === 'service_account' ) . map ( toOption ) ,
305+ {
306+ label : labels ?. serviceAccountConnect ?? `Add ${ getProviderName ( provider ) } key` ,
307+ value : '__connect_service_account__' ,
308+ iconElement : < ExternalLink className = 'size-3' /> ,
309+ } ,
310+ ] ,
311+ } ,
312+ ]
313+ } , [
314+ isMergedKinds ,
315+ subBlock . credentialLabels ,
316+ credentials ,
272317 provider ,
273318 getProviderIcon ,
274319 getProviderName ,
@@ -320,9 +365,17 @@ export function CredentialSelector({
320365 const handleComboboxChange = useCallback (
321366 ( value : string ) => {
322367 if ( value === '__connect_account__' ) {
368+ if ( isMergedKinds ) {
369+ setShowConnectModal ( true )
370+ return
371+ }
323372 handleAddCredential ( )
324373 return
325374 }
375+ if ( value === '__connect_service_account__' ) {
376+ setShowSetupModal ( true )
377+ return
378+ }
326379
327380 const matchedCred = (
328381 isAllCredentials ? allWorkspaceCredentials . filter ( ( c ) => c . type === 'oauth' ) : credentials
@@ -335,13 +388,21 @@ export function CredentialSelector({
335388 setIsEditing ( true )
336389 setEditingValue ( value )
337390 } ,
338- [ isAllCredentials , allWorkspaceCredentials , credentials , handleAddCredential , handleSelect ]
391+ [
392+ isAllCredentials ,
393+ isMergedKinds ,
394+ allWorkspaceCredentials ,
395+ credentials ,
396+ handleAddCredential ,
397+ handleSelect ,
398+ ]
339399 )
340400
341401 return (
342402 < div >
343403 < Combobox
344404 options = { comboboxOptions }
405+ groups = { comboboxGroups }
345406 value = { displayValue }
346407 selectedValue = { selectedId }
347408 onChange = { handleComboboxChange }
@@ -350,8 +411,10 @@ export function CredentialSelector({
350411 hasDependencies && ! depsSatisfied ? 'Fill in required fields above first' : label
351412 }
352413 disabled = { effectiveDisabled }
353- editable = { true }
354- filterOptions = { true }
414+ editable = { ! isMergedKinds }
415+ filterOptions = { ! isMergedKinds }
416+ searchable = { isMergedKinds }
417+ searchPlaceholder = 'Search credentials...'
355418 isLoading = { credentialsLoading }
356419 overlayContent = { overlayContent }
357420 className = { overlayContent ? 'pl-7' : '' }
@@ -371,7 +434,7 @@ export function CredentialSelector({
371434 workflowId : activeWorkflowId || '' ,
372435 displayName : selectedCredential ?. name ?? getProviderName ( provider ) ,
373436 providerId : effectiveProviderId ,
374- preCount : credentials . length ,
437+ preCount : credentials . filter ( ( c ) => c . type !== 'service_account' ) . length ,
375438 workspaceId,
376439 requestedAt : Date . now ( ) ,
377440 } )
@@ -417,22 +480,10 @@ export function CredentialSelector({
417480 />
418481 ) }
419482
420- { showSlackBotModal && (
421- < ConnectSlackBotModal
422- open = { showSlackBotModal }
423- onOpenChange = { setShowSlackBotModal }
424- workspaceId = { workspaceId }
425- onCreated = { ( newCredentialId ) => {
426- setStoreValue ( newCredentialId )
427- refetchCredentials ( )
428- } }
429- />
430- ) }
431-
432- { showServiceAccountModal && serviceAccountService ?. serviceAccountProviderId && (
483+ { showSetupModal && serviceAccountService ?. serviceAccountProviderId && (
433484 < ConnectServiceAccountModal
434- open = { showServiceAccountModal }
435- onOpenChange = { setShowServiceAccountModal }
485+ open = { showSetupModal }
486+ onOpenChange = { setShowSetupModal }
436487 workspaceId = { workspaceId }
437488 serviceAccountProviderId = {
438489 serviceAccountService . serviceAccountProviderId as ServiceAccountProviderId
0 commit comments