feat: Show alerts when the limit is reached in accounts (#7323)

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
Pranav Raj S
2023-06-15 20:47:11 -07:00
committed by GitHub
parent e8a27bea4b
commit 86b2896333
8 changed files with 202 additions and 2 deletions

View File

@@ -6,6 +6,10 @@
:class="{ 'app-rtl--wrapper': isRTLView }"
>
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
<payment-pending-banner />
<upgrade-banner />
</template>
<transition name="fade" mode="out-in">
<router-view />
</transition>
@@ -25,6 +29,8 @@ import AddAccountModal from '../dashboard/components/layout/sidebarComponents/Ad
import LoadingState from './components/widgets/LoadingState.vue';
import NetworkNotification from './components/NetworkNotification';
import UpdateBanner from './components/app/UpdateBanner.vue';
import UpgradeBanner from './components/app/UpgradeBanner.vue';
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
import vueActionCable from './helper/actionCable';
import WootSnackbarBox from './components/SnackbarContainer';
import rtlMixin from 'shared/mixins/rtlMixin';
@@ -41,7 +47,9 @@ export default {
LoadingState,
NetworkNotification,
UpdateBanner,
PaymentPendingBanner,
WootSnackbarBox,
UpgradeBanner,
},
mixins: [rtlMixin],
@@ -59,6 +67,7 @@ export default {
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
authUIFlags: 'getAuthUIFlags',
accountUIFlags: 'accounts/getUIFlags',
currentAccountId: 'getCurrentAccountId',
}),
hasAccounts() {

View File

@@ -13,6 +13,10 @@ class EnterpriseAccountAPI extends ApiClient {
subscription() {
return axios.post(`${this.url}subscription`);
}
getLimits() {
return axios.get(`${this.url}limits`);
}
}
export default new EnterpriseAccountAPI();

View File

@@ -0,0 +1,84 @@
<template>
<banner
v-if="shouldShowBanner"
color-scheme="alert"
:banner-message="bannerMessage"
:action-button-label="actionButtonMessage"
has-action-button
@click="routeToBilling"
/>
</template>
<script>
import Banner from 'dashboard/components/ui/Banner.vue';
import { mapGetters } from 'vuex';
import adminMixin from 'dashboard/mixins/isAdmin';
import accountMixin from 'dashboard/mixins/account';
const EMPTY_SUBSCRIPTION_INFO = {
status: null,
endsOn: null,
};
export default {
components: { Banner },
mixins: [adminMixin, accountMixin],
computed: {
...mapGetters({
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
getAccount: 'accounts/getAccount',
}),
bannerMessage() {
return this.$t('GENERAL_SETTINGS.PAYMENT_PENDING');
},
actionButtonMessage() {
return this.$t('GENERAL_SETTINGS.OPEN_BILLING');
},
shouldShowBanner() {
if (!this.isOnChatwootCloud) {
return false;
}
if (!this.isAdmin) {
return false;
}
return this.isPaymentPending();
},
},
methods: {
routeToBilling() {
this.$router.push({
name: 'billing_settings_index',
params: { accountId: this.accountId },
});
},
isPaymentPending() {
const { status, endsOn } = this.getSubscriptionInfo();
if (status && endsOn) {
const now = new Date();
if (status === 'past_due' && endsOn < now) {
return true;
}
}
return false;
},
getSubscriptionInfo() {
const account = this.getAccount(this.accountId);
if (!account) return EMPTY_SUBSCRIPTION_INFO;
const { custom_attributes: subscription } = account;
if (!subscription) return EMPTY_SUBSCRIPTION_INFO;
const {
subscription_status: status,
subscription_ends_on: endsOn,
} = subscription;
return { status, endsOn: new Date(endsOn) };
},
},
};
</script>

View File

@@ -0,0 +1,89 @@
<template>
<banner
v-if="shouldShowBanner"
color-scheme="alert"
:banner-message="bannerMessage"
:action-button-label="actionButtonMessage"
has-action-button
@click="routeToBilling"
/>
</template>
<script>
import Banner from 'dashboard/components/ui/Banner.vue';
import { mapGetters } from 'vuex';
import adminMixin from 'dashboard/mixins/isAdmin';
import accountMixin from 'dashboard/mixins/account';
import { differenceInDays } from 'date-fns';
export default {
components: { Banner },
mixins: [adminMixin, accountMixin],
data() {
return { conversationMeta: {} };
},
computed: {
...mapGetters({
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
getAccount: 'accounts/getAccount',
}),
bannerMessage() {
return this.$t('GENERAL_SETTINGS.LIMITS_UPGRADE');
},
actionButtonMessage() {
return this.$t('GENERAL_SETTINGS.OPEN_BILLING');
},
shouldShowBanner() {
if (!this.isOnChatwootCloud) {
return false;
}
if (this.isTrialAccount()) {
return false;
}
return this.isLimitExceeded();
},
},
mounted() {
if (this.isOnChatwootCloud) {
this.fetchLimits();
}
},
methods: {
fetchLimits() {
this.$store.dispatch('accounts/limits');
},
routeToBilling() {
this.$router.push({
name: 'billing_settings_index',
params: { accountId: this.accountId },
});
},
isTrialAccount() {
// check if account is less than 15 days old
const account = this.getAccount(this.accountId);
if (!account) return false;
const createdAt = new Date(account.created_at);
const diffDays = differenceInDays(new Date(), createdAt);
return diffDays <= 15;
},
isLimitExceeded() {
const account = this.getAccount(this.accountId);
if (!account) return false;
const { limits } = account;
if (!limits) return false;
const { conversation, non_web_inboxes: nonWebInboxes } = limits;
return this.testLimit(conversation) || this.testLimit(nonWebInboxes);
},
testLimit({ allowed, consumed }) {
return consumed > allowed;
},
},
};
</script>

View File

@@ -75,7 +75,7 @@ export default {
},
computed: {
bannerClasses() {
const classList = [this.colorScheme, `banner-align-${this.align}`];
const classList = [this.colorScheme];
if (this.hasActionButton || this.hasCloseButton) {
classList.push('has-button');

View File

@@ -48,7 +48,10 @@
}
},
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance.",
"LEARN_MORE": "Learn more"
"LEARN_MORE": "Learn more",
"PAYMENT_PENDING": "Your payment is pending. Please update your payment information to continue using Chatwoot",
"LIMITS_UPGRADE": "Your account has exceeded the usage limits, please upgrade your plan to continue using Chatwoot",
"OPEN_BILLING": "Open billing"
},
"FORMS": {
"MULTISELECT": {

View File

@@ -97,6 +97,15 @@ export const actions = {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isCheckoutInProcess: false });
}
},
limits: async ({ commit }) => {
try {
const response = await EnterpriseAccountAPI.getLimits();
commit(types.default.SET_ACCOUNT_LIMITS, response.data);
} catch (error) {
// silent error
}
},
};
export const mutations = {
@@ -108,6 +117,7 @@ export const mutations = {
},
[types.default.ADD_ACCOUNT]: MutationHelpers.setSingleRecord,
[types.default.EDIT_ACCOUNT]: MutationHelpers.update,
[types.default.SET_ACCOUNT_LIMITS]: MutationHelpers.updateAttributes,
};
export default {

View File

@@ -65,6 +65,7 @@ export default {
// Agent
SET_ACCOUNT_UI_FLAG: 'SET_ACCOUNT_UI_FLAG',
SET_ACCOUNT_LIMITS: 'SET_ACCOUNT_LIMITS',
SET_ACCOUNTS: 'SET_ACCOUNTS',
ADD_ACCOUNT: 'ADD_ACCOUNT',
EDIT_ACCOUNT: 'EDIT_ACCOUNT',