feat(editor): Implement new banners framework (#6603)
* ⚡ Implemented new grid row - banners * ✨ Fixing node creator and executions sidebar position after layout update * 💄 Added configurable round corners to the Callout component * ⚡ Fixing mouse position detection and main tab bar position * ⚡ Implemented basic banner component structure * ⚡ Implemented banner state and dismiss logic * ⚡ Fixing grid layout. Updating banners height state dynamically * ⚡ Fix zoom to fit position, mouse position in demo mode and callout vertical alignment * ⚡ Implementing proper trial banners logic * 💄 Only showing execution usage data once the sidebar is fully expanded * ✨ Implemented permanent/temporary dismiss logic for v1 flag * ⚡ Minor refactoring of banner logic * ⚡ Updating permanent dismiss logic to work with all banners * 👕 Fixing linting errors * ✔️ Updating Callout component test snapshots * 💄 Tweaking zoom to fit position * ✔️ Updating testing endpoints to use new store data * ✅ Added banners unit tests * ✔️ Fixing failing banner tests * ✅ Added more banner tests * ⚡ Updating banners dimensions on resize, removing leftover code * ✔️ Removing store import from API file * 👕 Fixing lint errors * ⚡ Updating migration files * ⚡ Using query parameters in migrations * 👌 Addressing design review feedback * ⚡ Updating upgrade plan button click * ⚡ Updating the migrations syntax * 👌 Updating permanent banner dismiss endpoint and back-end logic * 👌 Refactoring trial banner component and ui store * 👌 Addressing more points from code review * 👌 Moving DOM logic from the store * ✔️ Updated callout component snapshots * 👌 Updating mysql migration file * ✔️ Updating e2e test canvas coordinates after setting it's position to absolute * 👌 Addressing back-end review feedback * 👌 Improving typing around banners * 👕 Fixing lint errors
This commit is contained in:
committed by
GitHub
parent
ff0759530d
commit
4240e76253
36
packages/editor-ui/src/components/banners/BannerStack.vue
Normal file
36
packages/editor-ui/src/components/banners/BannerStack.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import TrialOverBanner from '@/components/banners/TrialOverBanner.vue';
|
||||
import TrialBanner from '@/components/banners/TrialBanner.vue';
|
||||
import V1Banner from '@/components/banners/V1Banner.vue';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { onMounted, watch } from 'vue';
|
||||
import { getBannerRowHeight } from '@/utils';
|
||||
import type { Banners } from 'n8n-workflow';
|
||||
|
||||
const uiStore = useUIStore();
|
||||
|
||||
function shouldShowBanner(bannerName: Banners) {
|
||||
return uiStore.banners[bannerName].dismissed === false;
|
||||
}
|
||||
|
||||
async function updateCurrentBannerHeight() {
|
||||
const bannerHeight = await getBannerRowHeight();
|
||||
uiStore.updateBannersHeight(bannerHeight);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await updateCurrentBannerHeight();
|
||||
});
|
||||
|
||||
watch(uiStore.banners, async () => {
|
||||
await updateCurrentBannerHeight();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-test-id="banner-stack">
|
||||
<trial-over-banner v-if="shouldShowBanner('TRIAL_OVER')" />
|
||||
<trial-banner v-if="shouldShowBanner('TRIAL')" />
|
||||
<v1-banner v-if="shouldShowBanner('V1')" />
|
||||
</div>
|
||||
</template>
|
||||
64
packages/editor-ui/src/components/banners/BaseBanner.vue
Normal file
64
packages/editor-ui/src/components/banners/BaseBanner.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import type { Banners } from 'n8n-workflow';
|
||||
|
||||
interface Props {
|
||||
name: Banners;
|
||||
theme?: string;
|
||||
customIcon?: string;
|
||||
dismissible?: boolean;
|
||||
}
|
||||
|
||||
const uiStore = useUIStore();
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
theme: 'info',
|
||||
dismissible: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
async function onCloseClick() {
|
||||
await uiStore.dismissBanner(props.name);
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n8n-callout
|
||||
:theme="props.theme"
|
||||
:icon="props.customIcon"
|
||||
iconSize="medium"
|
||||
:roundCorners="false"
|
||||
:data-test-id="`banners-${props.name}`"
|
||||
>
|
||||
<div :class="$style.mainContent">
|
||||
<slot name="mainContent" />
|
||||
</div>
|
||||
<template #trailingContent>
|
||||
<div :class="$style.trailingContent">
|
||||
<slot name="trailingContent" />
|
||||
<n8n-icon
|
||||
v-if="dismissible"
|
||||
size="small"
|
||||
icon="times"
|
||||
title="Dismiss"
|
||||
class="clickable"
|
||||
:data-test-id="`banner-${props.name}-close`"
|
||||
@click="onCloseClick"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</n8n-callout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.mainContent {
|
||||
display: flex;
|
||||
gap: var(--spacing-4xs);
|
||||
}
|
||||
.trailingContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
</style>
|
||||
36
packages/editor-ui/src/components/banners/TrialBanner.vue
Normal file
36
packages/editor-ui/src/components/banners/TrialBanner.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import BaseBanner from '@/components/banners/BaseBanner.vue';
|
||||
import { i18n as locale } from '@/plugins/i18n';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import { computed } from 'vue';
|
||||
import { useUIStore } from '@/stores';
|
||||
|
||||
const trialDaysLeft = computed(() => {
|
||||
const { trialDaysLeft } = useCloudPlanStore();
|
||||
return -1 * trialDaysLeft;
|
||||
});
|
||||
|
||||
const messageText = computed(() => {
|
||||
return locale.baseText('banners.trial.message', {
|
||||
adjustToNumber: trialDaysLeft.value,
|
||||
interpolate: { count: String(trialDaysLeft.value) },
|
||||
});
|
||||
});
|
||||
|
||||
function onUpdatePlanClick() {
|
||||
useUIStore().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<base-banner name="TRIAL" theme="custom">
|
||||
<template #mainContent>
|
||||
<span>{{ messageText }}</span>
|
||||
</template>
|
||||
<template #trailingContent>
|
||||
<n8n-button type="success" @click="onUpdatePlanClick" icon="gem" size="small">{{
|
||||
locale.baseText('generic.upgradeNow')
|
||||
}}</n8n-button>
|
||||
</template>
|
||||
</base-banner>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import BaseBanner from '@/components/banners/BaseBanner.vue';
|
||||
import { i18n as locale } from '@/plugins/i18n';
|
||||
import { useUIStore } from '@/stores';
|
||||
|
||||
function onUpdatePlanClick() {
|
||||
useUIStore().goToUpgrade('canvas-nav', 'upgrade-canvas-nav', 'redirect');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<base-banner customIcon="info-circle" theme="warning" name="TRIAL_OVER">
|
||||
<template #mainContent>
|
||||
<span>{{ locale.baseText('banners.trialOver.message') }}</span>
|
||||
</template>
|
||||
<template #trailingContent>
|
||||
<n8n-button type="success" @click="onUpdatePlanClick" icon="gem" size="small">{{
|
||||
locale.baseText('generic.upgradeNow')
|
||||
}}</n8n-button>
|
||||
</template>
|
||||
</base-banner>
|
||||
</template>
|
||||
38
packages/editor-ui/src/components/banners/V1Banner.vue
Normal file
38
packages/editor-ui/src/components/banners/V1Banner.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import BaseBanner from '@/components/banners/BaseBanner.vue';
|
||||
import { i18n as locale } from '@/plugins/i18n';
|
||||
import { useUsersStore } from '@/stores';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
|
||||
const uiStore = useUIStore();
|
||||
|
||||
const { isInstanceOwner } = useUsersStore();
|
||||
|
||||
async function dismissPermanently() {
|
||||
await uiStore.dismissBanner('V1', 'permanent');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<base-banner customIcon="info-circle" theme="warning" name="V1">
|
||||
<template #mainContent>
|
||||
<span v-html="locale.baseText('banners.v1.message')"></span>
|
||||
<a
|
||||
v-if="isInstanceOwner"
|
||||
:class="$style.link"
|
||||
@click="dismissPermanently"
|
||||
data-test-id="banner-confirm-v1"
|
||||
>
|
||||
<span v-html="locale.baseText('generic.dontShowAgain')"></span>
|
||||
</a>
|
||||
</template>
|
||||
</base-banner>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
a,
|
||||
.link {
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user