diff --git a/apps/bubble-studio/src/components/BubbleNode.tsx b/apps/bubble-studio/src/components/BubbleNode.tsx index db2570d6..07ecd15b 100644 --- a/apps/bubble-studio/src/components/BubbleNode.tsx +++ b/apps/bubble-studio/src/components/BubbleNode.tsx @@ -2,11 +2,16 @@ import { memo, useMemo, useState } from 'react'; import { Handle, Position } from '@xyflow/react'; import { CogIcon } from '@heroicons/react/24/outline'; import { BookOpen, Code, Info, Sparkles } from 'lucide-react'; -import { CredentialType } from '@bubblelab/shared-schemas'; +import { + CredentialType, + SYSTEM_CREDENTIALS, + getAlternativeCredentialsGroup, + getCredentialTypeLabel, + validateCredentialSelection, +} from '@bubblelab/shared-schemas'; import { CreateCredentialModal } from '../pages/CredentialsPage'; import { useCreateCredential } from '../hooks/useCredentials'; import { findLogoForBubble, findDocsUrlForBubble } from '../lib/integrations'; -import { SYSTEM_CREDENTIALS } from '@bubblelab/shared-schemas'; import type { ParsedBubbleWithInfo } from '@bubblelab/shared-schemas'; import BubbleExecutionBadge from './BubbleExecutionBadge'; import { BUBBLE_COLORS, BADGE_COLORS } from './BubbleColors'; @@ -169,12 +174,12 @@ function BubbleNode({ data }: BubbleNodeProps) { return Object.keys(credValue); }, [propRequiredCredentialTypes, bubble.parameters]); - // Check if credentials are missing - const hasMissingRequirements = requiredCredentialTypes.some((credType) => { - if (SYSTEM_CREDENTIALS.has(credType as CredentialType)) return false; - const selectedId = selectedBubbleCredentials[credType]; - return selectedId === undefined || selectedId === null; - }); + // Check if credentials are missing using centralized validation + const hasMissingRequirements = !validateCredentialSelection( + requiredCredentialTypes.map((t) => t as CredentialType), + selectedBubbleCredentials, + SYSTEM_CREDENTIALS + ).isValid; const handleCredentialChange = (credType: string, credId: number | null) => { setCredential(credentialsKey, credType, credId); @@ -574,72 +579,159 @@ function BubbleNode({ data }: BubbleNodeProps) { return null; } + // Check if all credential types form a group (alternatives like OAuth vs API Key) + const alternativeGroup = getAlternativeCredentialsGroup( + filteredCredentialTypes.map((t) => t as CredentialType) + ); + return (
+ {groupCreds.length} credential + {groupCreds.length !== 1 ? 's' : ''} +
+