Skip to content

Commit

Permalink
feat: Show alerts when the limit is reached in accounts (#7323)
Browse files Browse the repository at this point in the history
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
  • Loading branch information
pranavrajs and scmmishra committed Jun 16, 2023
1 parent e8a27be commit 86b2896
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 2 deletions.
9 changes: 9 additions & 0 deletions app/javascript/dashboard/App.vue
Expand Up @@ -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>
Expand All @@ -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';
Expand All @@ -41,7 +47,9 @@ export default {
LoadingState,
NetworkNotification,
UpdateBanner,
PaymentPendingBanner,
WootSnackbarBox,
UpgradeBanner,
},
mixins: [rtlMixin],
Expand All @@ -59,6 +67,7 @@ export default {
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
authUIFlags: 'getAuthUIFlags',
accountUIFlags: 'accounts/getUIFlags',
currentAccountId: 'getCurrentAccountId',
}),
hasAccounts() {
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/dashboard/api/enterprise/account.js
Expand Up @@ -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();
84 changes: 84 additions & 0 deletions app/javascript/dashboard/components/app/PaymentPendingBanner.vue
@@ -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>
89 changes: 89 additions & 0 deletions app/javascript/dashboard/components/app/UpgradeBanner.vue
@@ -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>
2 changes: 1 addition & 1 deletion app/javascript/dashboard/components/ui/Banner.vue
Expand Up @@ -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');
Expand Down
5 changes: 4 additions & 1 deletion app/javascript/dashboard/i18n/locale/en/generalSettings.json
Expand Up @@ -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": {
Expand Down
10 changes: 10 additions & 0 deletions app/javascript/dashboard/store/modules/accounts.js
Expand Up @@ -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 = {
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions app/javascript/dashboard/store/mutation-types.js
Expand Up @@ -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',
Expand Down

0 comments on commit 86b2896

Please sign in to comment.