diff --git a/packages/frontend/@n8n/rest-api-client/src/api/cloudPlans.ts b/packages/frontend/@n8n/rest-api-client/src/api/cloudPlans.ts index 16f1b397143..6be93b51c95 100644 --- a/packages/frontend/@n8n/rest-api-client/src/api/cloudPlans.ts +++ b/packages/frontend/@n8n/rest-api-client/src/api/cloudPlans.ts @@ -35,6 +35,9 @@ export declare namespace Cloud { information?: { [key: string]: string | string[]; }; + trialBannerData?: { + bannerText: string; + }; }; } diff --git a/packages/frontend/editor-ui/src/app/stores/cloudPlan.store.ts b/packages/frontend/editor-ui/src/app/stores/cloudPlan.store.ts index 71ba7f06403..9f94a10e68b 100644 --- a/packages/frontend/editor-ui/src/app/stores/cloudPlan.store.ts +++ b/packages/frontend/editor-ui/src/app/stores/cloudPlan.store.ts @@ -22,12 +22,17 @@ const DEFAULT_STATE: CloudPlanState = { loadingPlan: false, }; +const DYNAMIC_TRIAL_BANNER_DISMISSED_KEY = 'n8n-dynamic-trial-banner-dismissed'; + export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => { const rootStore = useRootStore(); const settingsStore = useSettingsStore(); const state = reactive(DEFAULT_STATE); const currentUserCloudInfo = ref(null); + const isDynamicTrialBannerDismissed = ref( + localStorage.getItem(DYNAMIC_TRIAL_BANNER_DISMISSED_KEY) === 'true', + ); const reset = () => { currentUserCloudInfo.value = null; @@ -61,6 +66,23 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => { return information.which_of_these_do_you_feel_comfortable_doing.length; }); + const dynamicTrialBannerText = computed(() => { + return currentUserCloudInfo.value?.trialBannerData?.bannerText; + }); + + const shouldShowDynamicTrialBanner = computed(() => { + return ( + dynamicTrialBannerText.value !== undefined && + dynamicTrialBannerText.value !== '' && + !isDynamicTrialBannerDismissed.value + ); + }); + + const dismissDynamicTrialBanner = () => { + isDynamicTrialBannerDismissed.value = true; + localStorage.setItem(DYNAMIC_TRIAL_BANNER_DISMISSED_KEY, 'true'); + }; + const trialExpired = computed( () => state.data?.metadata?.group === 'trial' && @@ -217,5 +239,8 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => { getAutoLoginCode, selectedApps, codingSkill, + dynamicTrialBannerText, + shouldShowDynamicTrialBanner, + dismissDynamicTrialBanner, }; }); diff --git a/packages/frontend/editor-ui/src/features/shared/banners/components/banners/TrialBanner.vue b/packages/frontend/editor-ui/src/features/shared/banners/components/banners/TrialBanner.vue index 72d2dd5359f..146c43ac078 100644 --- a/packages/frontend/editor-ui/src/features/shared/banners/components/banners/TrialBanner.vue +++ b/packages/frontend/editor-ui/src/features/shared/banners/components/banners/TrialBanner.vue @@ -6,13 +6,16 @@ import { computed } from 'vue'; import type { CloudPlanAndUsageData } from '@/Interface'; import { usePageRedirectionHelper } from '@/app/composables/usePageRedirectionHelper'; -import { N8nButton, N8nText } from '@n8n/design-system'; +import { N8nBadge, N8nButton, N8nText } from '@n8n/design-system'; const PROGRESS_BAR_MINIMUM_THRESHOLD = 8; const cloudPlanStore = useCloudPlanStore(); const pageRedirectionHelper = usePageRedirectionHelper(); +const shouldShowTrialBanner = computed(() => cloudPlanStore.shouldShowDynamicTrialBanner); +const trialBannerText = computed(() => cloudPlanStore.dynamicTrialBannerText); + const trialDaysLeft = computed(() => -1 * cloudPlanStore.trialDaysLeft); const messageText = computed(() => { return locale.baseText('banners.trial.message', { @@ -52,6 +55,10 @@ const currentExecutions = computed(() => { }); function onUpdatePlanClick() { + if (shouldShowTrialBanner.value) { + cloudPlanStore.dismissDynamicTrialBanner(); + } + void pageRedirectionHelper.goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect'); } @@ -86,9 +93,19 @@ function onUpdatePlanClick() { @@ -174,4 +191,18 @@ function onUpdatePlanClick() { .executionsCountSection { margin-left: var(--spacing--xs); } + +.trailingContentWrapper { + display: flex; + align-items: center; + gap: var(--spacing--2xs); +} + +.dynamicBanner { + color: var(--color--foreground--tint-2); + border: none; + background: var(--color--foreground--shade-2); + border-radius: var(--radius); + padding: var(--spacing--5xs) var(--spacing--3xs); +}