feat: Upgrade page instead of banner (#11202)

# Pull Request Template

## Description

This PR will replace the upgrade banner with an upgrade page view.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

### Loom video

https://www.loom.com/share/0f2b4b09acdd4404bf3211184a470227?sid=7ed60a99-0299-4642-b907-2af8c4dcc643


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Sivin Varghese
2025-03-28 14:58:17 +05:30
committed by GitHub
parent 0175714d65
commit 4e58a2a91d
8 changed files with 255 additions and 48 deletions

View File

@@ -1,6 +1,6 @@
<script>
import { defineAsyncComponent, ref } from 'vue';
import { mapGetters } from 'vuex';
import { defineAsyncComponent } from 'vue';
import NextSidebar from 'next/sidebar/Sidebar.vue';
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal.vue';
@@ -8,6 +8,7 @@ import AddAccountModal from 'dashboard/components/layout/sidebarComponents/AddAc
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector.vue';
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
import UpgradePage from 'dashboard/routes/dashboard/upgrade/UpgradePage.vue';
import { useUISettings } from 'dashboard/composables/useUISettings';
import { useAccount } from 'dashboard/composables/useAccount';
@@ -35,8 +36,10 @@ export default {
AccountSelector,
AddLabelModal,
NotificationPanel,
UpgradePage,
},
setup() {
const upgradePageRef = ref(null);
const { uiSettings, updateUISettings } = useUISettings();
const { accountId } = useAccount();
@@ -44,6 +47,7 @@ export default {
uiSettings,
updateUISettings,
accountId,
upgradePageRef,
};
},
data() {
@@ -64,6 +68,16 @@ export default {
currentRoute() {
return ' ';
},
showUpgradePage() {
return this.upgradePageRef?.shouldShowUpgradePage;
},
bypassUpgradePage() {
return [
'billing_settings_index',
'settings_inbox_list',
'agent_list',
].includes(this.$route.name);
},
isSidebarOpen() {
const { show_secondary_sidebar: showSecondarySidebar } = this.uiSettings;
return showSecondarySidebar;
@@ -197,8 +211,25 @@ export default {
@show-add-label-popup="showAddLabelPopup"
/>
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
<router-view />
<CommandBar />
<UpgradePage
v-show="showUpgradePage"
ref="upgradePageRef"
:bypass-upgrade-page="bypassUpgradePage"
/>
<template v-if="!showUpgradePage">
<router-view />
<CommandBar />
<NotificationPanel
v-if="isNotificationPanel"
@close="closeNotificationPanel"
/>
<woot-modal
v-model:show="showAddLabelModal"
:on-close="hideAddLabelPopup"
>
<AddLabelModal @close="hideAddLabelPopup" />
</woot-modal>
</template>
<AccountSelector
:show-account-modal="showAccountModal"
@close-account-modal="toggleAccountModal"
@@ -213,16 +244,6 @@ export default {
@close="closeKeyShortcutModal"
@clickaway="closeKeyShortcutModal"
/>
<NotificationPanel
v-if="isNotificationPanel"
@close="closeNotificationPanel"
/>
<woot-modal
v-model:show="showAddLabelModal"
:on-close="hideAddLabelPopup"
>
<AddLabelModal @close="hideAddLabelPopup" />
</woot-modal>
</main>
</div>
</template>

View File

@@ -0,0 +1,145 @@
<script setup>
import { onMounted, computed, defineExpose, defineProps } from 'vue';
import { useStore } from 'dashboard/composables/store';
import { useMapGetter } from 'dashboard/composables/store.js';
import { useRouter } from 'vue-router';
import { useAccount } from 'dashboard/composables/useAccount';
import { differenceInDays } from 'date-fns';
import { useAdmin } from 'dashboard/composables/useAdmin';
import { useI18n } from 'vue-i18n';
import NextButton from 'dashboard/components-next/button/Button.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
const props = defineProps({
bypassUpgradePage: {
type: Boolean,
default: false,
},
});
const router = useRouter();
const store = useStore();
const { t } = useI18n();
const { accountId, currentAccount } = useAccount();
const { isAdmin } = useAdmin();
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
const testLimit = ({ allowed, consumed }) => {
return consumed > allowed;
};
const isTrialAccount = computed(() => {
// check if account is less than 15 days old
const account = currentAccount.value;
if (!account) return false;
const createdAt = new Date(account.created_at);
const diffDays = differenceInDays(new Date(), createdAt);
return diffDays <= 15;
});
const limitExceededMessage = computed(() => {
const account = currentAccount.value;
if (!account?.limits) return '';
const {
conversation,
non_web_inboxes: nonWebInboxes,
agents,
} = account.limits;
let message = '';
if (testLimit(conversation)) {
message = t('GENERAL_SETTINGS.LIMIT_MESSAGES.CONVERSATION');
} else if (testLimit(nonWebInboxes)) {
message = t('GENERAL_SETTINGS.LIMIT_MESSAGES.INBOXES');
} else if (testLimit(agents)) {
message = t('GENERAL_SETTINGS.LIMIT_MESSAGES.AGENTS');
}
return message;
});
const isLimitExceeded = computed(() => {
const account = currentAccount.value;
if (!account?.limits) return false;
const {
conversation,
non_web_inboxes: nonWebInboxes,
agents,
} = account.limits;
return (
testLimit(conversation) || testLimit(nonWebInboxes) || testLimit(agents)
);
});
const shouldShowUpgradePage = computed(() => {
// Skip upgrade page in Billing, Inbox, and Agent pages
if (props.bypassUpgradePage) return false;
if (!isOnChatwootCloud.value) return false;
if (isTrialAccount.value) return false;
return isLimitExceeded.value;
});
const fetchLimits = () => {
store.dispatch('accounts/limits');
};
const routeToBilling = () => {
router.push({
name: 'billing_settings_index',
params: { accountId: accountId.value },
});
};
onMounted(() => fetchLimits());
defineExpose({ shouldShowUpgradePage });
</script>
<template>
<template v-if="shouldShowUpgradePage">
<div class="mx-auto h-full pt-[clamp(3rem,15vh,12rem)]">
<div
class="flex flex-col gap-4 max-w-md px-8 py-6 shadow-lg bg-n-solid-1 rounded-xl outline outline-1 outline-n-container"
>
<div class="flex flex-col gap-4">
<div class="flex items-center w-full gap-2">
<span
class="flex items-center justify-center w-6 h-6 rounded-full bg-n-solid-blue"
>
<Icon
class="flex-shrink-0 text-n-brand size-[14px]"
icon="i-lucide-lock-keyhole"
/>
</span>
<span class="text-base font-medium text-n-slate-12">
{{ $t('GENERAL_SETTINGS.UPGRADE') }}
</span>
</div>
<div>
<p class="text-sm font-normal text-n-slate-11 mb-3">
{{ limitExceededMessage }}
</p>
<p v-if="!isAdmin">
{{ t('GENERAL_SETTINGS.LIMIT_MESSAGES.NON_ADMIN') }}
</p>
</div>
</div>
<NextButton
v-if="isAdmin"
:label="$t('GENERAL_SETTINGS.OPEN_BILLING')"
icon="i-lucide-credit-card"
@click="routeToBilling()"
/>
</div>
</div>
</template>
<template v-else />
</template>