diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx index 90cababca5b..a8d6a80ca83 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx @@ -900,7 +900,11 @@ export function KnowledgeBase({ onClick={() => setShowConnectorsModal(true)} className='flex shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption shadow-[inset_0_0_0_1px_var(--border)] transition-colors hover-hover:bg-[var(--surface-3)]' > - {ConnectorIcon && } + {connector.status === 'syncing' ? ( + + ) : ( + ConnectorIcon && + )} {def?.name || connector.connectorType} ) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx index 5144ce5d8ab..9e266ec4e80 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx @@ -19,21 +19,17 @@ import { ModalHeader, Tooltip, } from '@/components/emcn' -import { useSession } from '@/lib/auth/auth-client' -import { consumeOAuthReturnContext, writeOAuthReturnContext } from '@/lib/credentials/client-state' -import { - getCanonicalScopesForProvider, - getProviderIdFromServiceId, - type OAuthProvider, -} from '@/lib/oauth' +import { consumeOAuthReturnContext } from '@/lib/credentials/client-state' +import { getProviderIdFromServiceId, type OAuthProvider } from '@/lib/oauth' import { ConnectorSelectorField } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/components/connector-selector-field' -import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal' +import { ConnectCredentialModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/connect-credential-modal' import { getDependsOnFields } from '@/blocks/utils' import { CONNECTOR_REGISTRY } from '@/connectors/registry' import type { ConnectorConfig, ConnectorConfigField } from '@/connectors/types' import { useCreateConnector } from '@/hooks/queries/kb/connectors' import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials' import type { SelectorKey } from '@/hooks/selectors/types' +import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers' const SYNC_INTERVALS = [ { label: 'Every hour', value: 60 }, @@ -69,7 +65,6 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo const [searchTerm, setSearchTerm] = useState('') const { workspaceId } = useParams<{ workspaceId: string }>() - const { data: session } = useSession() const { mutate: createConnector, isPending: isCreating } = useCreateConnector() const connectorConfig = selectedType ? CONNECTOR_REGISTRY[selectedType] : null @@ -82,10 +77,16 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo [connectorConfig] ) - const { data: credentials = [], isLoading: credentialsLoading } = useOAuthCredentials( - connectorProviderId ?? undefined, - { enabled: Boolean(connectorConfig) && !isApiKeyMode, workspaceId } - ) + const { + data: credentials = [], + isLoading: credentialsLoading, + refetch: refetchCredentials, + } = useOAuthCredentials(connectorProviderId ?? undefined, { + enabled: Boolean(connectorConfig) && !isApiKeyMode, + workspaceId, + }) + + useCredentialRefreshTriggers(refetchCredentials, connectorProviderId ?? '', workspaceId) const effectiveCredentialId = selectedCredentialId ?? (credentials.length === 1 ? credentials[0].id : null) @@ -263,51 +264,9 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo ) } - const handleConnectNewAccount = useCallback(async () => { - if (!connectorConfig || !connectorProviderId || !workspaceId) return - - const userName = session?.user?.name - const integrationName = connectorConfig.name - const displayName = userName ? `${userName}'s ${integrationName}` : integrationName - - try { - const res = await fetch('/api/credentials/draft', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - workspaceId, - providerId: connectorProviderId, - displayName, - }), - }) - if (!res.ok) { - setError('Failed to prepare credential. Please try again.') - return - } - } catch { - setError('Failed to prepare credential. Please try again.') - return - } - - writeOAuthReturnContext({ - origin: 'kb-connectors', - knowledgeBaseId, - displayName, - providerId: connectorProviderId, - preCount: credentials.length, - workspaceId, - requestedAt: Date.now(), - }) - + const handleConnectNewAccount = useCallback(() => { setShowOAuthModal(true) - }, [ - connectorConfig, - connectorProviderId, - workspaceId, - session?.user?.name, - knowledgeBaseId, - credentials.length, - ]) + }, []) const filteredEntries = useMemo(() => { const term = searchTerm.toLowerCase().trim() @@ -396,40 +355,40 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo ) : (
- {credentialsLoading ? ( -
- - Loading credentials... -
- ) : ( - ({ - label: cred.name || cred.provider, - value: cred.id, - icon: connectorConfig.icon, - }) - ), - { - label: 'Connect new account', - value: '__connect_new__', - icon: Plus, - onSelect: () => { - void handleConnectNewAccount() - }, + ({ + label: cred.name || cred.provider, + value: cred.id, + icon: connectorConfig.icon, + }) + ), + { + label: + credentials.length > 0 + ? `Connect another ${connectorConfig.name} account` + : `Connect ${connectorConfig.name} account`, + value: '__connect_new__', + icon: Plus, + onSelect: () => { + void handleConnectNewAccount() }, - ]} - value={effectiveCredentialId ?? undefined} - onChange={(value) => setSelectedCredentialId(value)} - placeholder={ - credentials.length === 0 - ? `No ${connectorConfig.name} accounts` - : 'Select account' - } - /> - )} + }, + ]} + value={effectiveCredentialId ?? undefined} + onChange={(value) => setSelectedCredentialId(value)} + onOpenChange={(isOpen) => { + if (isOpen) void refetchCredentials() + }} + placeholder={ + credentials.length === 0 + ? `No ${connectorConfig.name} accounts` + : 'Select account' + } + isLoading={credentialsLoading} + />
)} @@ -590,20 +549,23 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo )} - {connectorConfig && connectorConfig.auth.mode === 'oauth' && connectorProviderId && ( - { - consumeOAuthReturnContext() - setShowOAuthModal(false) - }} - provider={connectorProviderId} - toolName={connectorConfig.name} - requiredScopes={getCanonicalScopesForProvider(connectorProviderId)} - newScopes={[]} - serviceId={connectorConfig.auth.provider} - /> - )} + {showOAuthModal && + connectorConfig && + connectorConfig.auth.mode === 'oauth' && + connectorProviderId && ( + { + consumeOAuthReturnContext() + setShowOAuthModal(false) + }} + provider={connectorProviderId} + serviceId={connectorConfig.auth.provider} + workspaceId={workspaceId} + knowledgeBaseId={knowledgeBaseId} + credentialCount={credentials.length} + /> + )} ) } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx index f7eeac07c8b..37049a212d2 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx @@ -36,6 +36,7 @@ import { } from '@/lib/oauth' import { getMissingRequiredScopes } from '@/lib/oauth/utils' import { EditConnectorModal } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal' +import { ConnectCredentialModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/connect-credential-modal' import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal' import { CONNECTOR_REGISTRY } from '@/connectors/registry' import type { ConnectorData, SyncLogData } from '@/hooks/queries/kb/connectors' @@ -46,6 +47,7 @@ import { useUpdateConnector, } from '@/hooks/queries/kb/connectors' import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials' +import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers' const logger = createLogger('ConnectorsSection') @@ -328,11 +330,16 @@ function ConnectorCard({ const requiredScopes = connectorDef?.auth.mode === 'oauth' ? (connectorDef.auth.requiredScopes ?? []) : [] - const { data: credentials } = useOAuthCredentials(providerId, { workspaceId }) + const { data: credentials, refetch: refetchCredentials } = useOAuthCredentials(providerId, { + workspaceId, + }) + + useCredentialRefreshTriggers(refetchCredentials, providerId ?? '', workspaceId) const missingScopes = useMemo(() => { if (!credentials || !connector.credentialId) return [] const credential = credentials.find((c) => c.id === connector.credentialId) + if (!credential) return [] return getMissingRequiredScopes(credential, requiredScopes) }, [credentials, connector.credentialId, requiredScopes]) @@ -484,15 +491,17 @@ function ConnectorCard({