From 190d7c0dc0fb6dcef315de97d8cb2d24579afdff Mon Sep 17 00:00:00 2001 From: Elias Meire Date: Thu, 1 Oct 2026 14:59:20 +0000 Subject: [PATCH] feat(editor): Show Gateway credits promotions on community nodes (#40010) --- .../api-types/src/community-node-types.ts | 8 + .../community-node-types-utils.ts | 2 + .../src/app/stores/nodeTypes.store.ts | 1 + .../CredentialEdit/CredentialConfig.vue | 7 + .../GatewayCreditsPromotion.test.ts | 14 ++ .../GatewayCreditsPromotion.vue | 11 ++ .../useGatewayCreditsPromotion.test.ts | 139 ++++++++++++++++++ .../useGatewayCreditsPromotion.ts | 71 +++++++++ .../ndv/settings/components/NodeSettings.vue | 17 +++ .../nodeCreator/CommunityNodeInfo.test.ts | 1 + .../nodeCreator/CommunityNodeInfo.vue | 11 ++ .../components/Modes/ActionsMode.vue | 22 ++- 12 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 packages/frontend/editor-ui/src/features/credentials/gatewayCreditsPromotion/GatewayCreditsPromotion.test.ts create mode 100644 packages/frontend/editor-ui/src/features/credentials/gatewayCreditsPromotion/GatewayCreditsPromotion.vue create mode 100644 packages/frontend/editor-ui/src/features/credentials/gatewayCreditsPromotion/useGatewayCreditsPromotion.test.ts create mode 100644 packages/frontend/editor-ui/src/features/credentials/gatewayCreditsPromotion/useGatewayCreditsPromotion.ts diff --git a/packages/@n8n/api-types/src/community-node-types.ts b/packages/@n8n/api-types/src/community-node-types.ts index 4c01bb3fd41..9aed59b7a84 100644 --- a/packages/@n8n/api-types/src/community-node-types.ts +++ b/packages/@n8n/api-types/src/community-node-types.ts @@ -1,5 +1,12 @@ import type { INodeTypeDescription } from 'n8n-workflow'; +/** Temporary Gateway credits offer for a community node. Dates are ISO datetimes. */ +export type GatewayCreditsPromotion = { + text: string; + startsAt?: string | null; + endsAt?: string | null; +}; + export type CommunityNodeType = { id: number; authorGithubUrl: string; @@ -19,4 +26,5 @@ export type CommunityNodeType = { nodeDescription: INodeTypeDescription; isInstalled: boolean; nodeVersions?: Array<{ npmVersion: string; checksum: string }>; + gatewayCreditsPromotion?: GatewayCreditsPromotion | null; }; diff --git a/packages/cli/src/modules/community-packages/community-node-types-utils.ts b/packages/cli/src/modules/community-packages/community-node-types-utils.ts index 8aca512b496..2fd43093a73 100644 --- a/packages/cli/src/modules/community-packages/community-node-types-utils.ts +++ b/packages/cli/src/modules/community-packages/community-node-types-utils.ts @@ -1,3 +1,4 @@ +import type { GatewayCreditsPromotion } from '@n8n/api-types'; import type { INodeTypeDescription } from 'n8n-workflow'; import { paginatedRequest, type StrapiFilters } from '@/utils/strapi-utils'; @@ -21,6 +22,7 @@ export type StrapiCommunityNodeType = { nodeDescription: INodeTypeDescription; nodeVersions?: Array<{ npmVersion: string; checksum: string }>; aiNodeSdkVersion?: number; + gatewayCreditsPromotion?: GatewayCreditsPromotion | null; }; /** diff --git a/packages/frontend/editor-ui/src/app/stores/nodeTypes.store.ts b/packages/frontend/editor-ui/src/app/stores/nodeTypes.store.ts index 90f0fb9ccc7..b6560a529de 100644 --- a/packages/frontend/editor-ui/src/app/stores/nodeTypes.store.ts +++ b/packages/frontend/editor-ui/src/app/stores/nodeTypes.store.ts @@ -574,6 +574,7 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, () => { visibleNodeTypesByInputConnectionTypeNames, isConfigurableNode, communityNodesAndActions, + vettedCommunityNodeTypes, communityNodeType, officialCommunityNodeTypes, unofficialCommunityNodeTypes, diff --git a/packages/frontend/editor-ui/src/features/credentials/components/CredentialEdit/CredentialConfig.vue b/packages/frontend/editor-ui/src/features/credentials/components/CredentialEdit/CredentialConfig.vue index 811f8e59a18..a4faedfa9bb 100644 --- a/packages/frontend/editor-ui/src/features/credentials/components/CredentialEdit/CredentialConfig.vue +++ b/packages/frontend/editor-ui/src/features/credentials/components/CredentialEdit/CredentialConfig.vue @@ -55,6 +55,8 @@ import CredentialTypeSelector from './CredentialTypeSelector.vue'; import { useQuickConnect } from '../../quickConnect/composables/useQuickConnect'; import QuickConnectButton from '../../quickConnect/components/QuickConnectButton.vue'; import QuickConnectBanner from '../../quickConnect/components/QuickConnectBanner.vue'; +import { useGatewayCreditsPromotion } from '../../gatewayCreditsPromotion/useGatewayCreditsPromotion'; +import GatewayCreditsPromotion from '../../gatewayCreditsPromotion/GatewayCreditsPromotion.vue'; import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store'; import { ProjectTypes } from '@/features/collaboration/projects/projects.types'; import type { ProjectType } from '@/features/collaboration/projects/projects.types'; @@ -129,6 +131,9 @@ const chatPanelStore = useChatPanelStore(); const i18n = useI18n(); const telemetry = useTelemetry(); const { getQuickConnectOption } = useQuickConnect(); +const { promotionText } = useGatewayCreditsPromotion({ + credentialType: () => props.credentialType.name, +}); onBeforeMount(async () => { uiStore.activeCredentialType = props.credentialType.name; @@ -461,6 +466,7 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => { :text="quickConnectBannerText" :disclaimer="quickConnectOption?.disclaimer" /> + { @@ -202,6 +210,9 @@ onMounted(async () => { .description { margin: var(--spacing--md) 0; } +.gatewayCreditsPromotion { + margin-top: var(--spacing--2xs); +} .separator { height: var(--border-width); background: var(--color--foreground); diff --git a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/ActionsMode.vue b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/ActionsMode.vue index 7e4623b71e3..f1244319a74 100644 --- a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/ActionsMode.vue +++ b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/ActionsMode.vue @@ -37,6 +37,8 @@ import CommunityNodeFooter from '@/features/settings/communityNodes/components/n import { useCalloutHelpers } from '@/app/composables/useCalloutHelpers'; import { useQuickConnect } from '@/features/credentials/quickConnect/composables/useQuickConnect'; import QuickConnectBanner from '@/features/credentials/quickConnect/components/QuickConnectBanner.vue'; +import { useGatewayCreditsPromotion } from '@/features/credentials/gatewayCreditsPromotion/useGatewayCreditsPromotion'; +import GatewayCreditsPromotion from '@/features/credentials/gatewayCreditsPromotion/GatewayCreditsPromotion.vue'; import { N8nCallout, N8nInfoTip } from '@n8n/design-system'; const emit = defineEmits<{ @@ -117,6 +119,14 @@ const quickConnect = computed(() => { return null; }); +// CommunityNodeInfo shows the promotion when community node details are open. +const { promotionText } = useGatewayCreditsPromotion({ + nodeType: () => { + const items = useViewStacks().activeViewStack.items; + return !communityNodeDetails.value && items?.length ? items[0].key : undefined; + }, +}); + const hasNoTriggerActions = computed( () => parseCategoryActions( @@ -273,8 +283,13 @@ const callouts = computed(() => []); -
- +
+ +