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:
parent
e8a27bea4b
commit
86b2896333
@ -6,6 +6,10 @@
|
|||||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||||
>
|
>
|
||||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||||
|
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
|
||||||
|
<payment-pending-banner />
|
||||||
|
<upgrade-banner />
|
||||||
|
</template>
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<router-view />
|
<router-view />
|
||||||
</transition>
|
</transition>
|
||||||
@ -25,6 +29,8 @@ import AddAccountModal from '../dashboard/components/layout/sidebarComponents/Ad
|
|||||||
import LoadingState from './components/widgets/LoadingState.vue';
|
import LoadingState from './components/widgets/LoadingState.vue';
|
||||||
import NetworkNotification from './components/NetworkNotification';
|
import NetworkNotification from './components/NetworkNotification';
|
||||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
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 vueActionCable from './helper/actionCable';
|
||||||
import WootSnackbarBox from './components/SnackbarContainer';
|
import WootSnackbarBox from './components/SnackbarContainer';
|
||||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||||
@ -41,7 +47,9 @@ export default {
|
|||||||
LoadingState,
|
LoadingState,
|
||||||
NetworkNotification,
|
NetworkNotification,
|
||||||
UpdateBanner,
|
UpdateBanner,
|
||||||
|
PaymentPendingBanner,
|
||||||
WootSnackbarBox,
|
WootSnackbarBox,
|
||||||
|
UpgradeBanner,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [rtlMixin],
|
mixins: [rtlMixin],
|
||||||
@ -59,6 +67,7 @@ export default {
|
|||||||
currentUser: 'getCurrentUser',
|
currentUser: 'getCurrentUser',
|
||||||
globalConfig: 'globalConfig/get',
|
globalConfig: 'globalConfig/get',
|
||||||
authUIFlags: 'getAuthUIFlags',
|
authUIFlags: 'getAuthUIFlags',
|
||||||
|
accountUIFlags: 'accounts/getUIFlags',
|
||||||
currentAccountId: 'getCurrentAccountId',
|
currentAccountId: 'getCurrentAccountId',
|
||||||
}),
|
}),
|
||||||
hasAccounts() {
|
hasAccounts() {
|
||||||
|
|||||||
@ -13,6 +13,10 @@ class EnterpriseAccountAPI extends ApiClient {
|
|||||||
subscription() {
|
subscription() {
|
||||||
return axios.post(`${this.url}subscription`);
|
return axios.post(`${this.url}subscription`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLimits() {
|
||||||
|
return axios.get(`${this.url}limits`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new EnterpriseAccountAPI();
|
export default new EnterpriseAccountAPI();
|
||||||
|
|||||||
@ -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
app/javascript/dashboard/components/app/UpgradeBanner.vue
Normal file
89
app/javascript/dashboard/components/app/UpgradeBanner.vue
Normal 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>
|
||||||
@ -75,7 +75,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
bannerClasses() {
|
bannerClasses() {
|
||||||
const classList = [this.colorScheme, `banner-align-${this.align}`];
|
const classList = [this.colorScheme];
|
||||||
|
|
||||||
if (this.hasActionButton || this.hasCloseButton) {
|
if (this.hasActionButton || this.hasCloseButton) {
|
||||||
classList.push('has-button');
|
classList.push('has-button');
|
||||||
|
|||||||
@ -48,7 +48,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance.",
|
"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": {
|
"FORMS": {
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
|
|||||||
@ -97,6 +97,15 @@ export const actions = {
|
|||||||
commit(types.default.SET_ACCOUNT_UI_FLAG, { isCheckoutInProcess: false });
|
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 = {
|
export const mutations = {
|
||||||
@ -108,6 +117,7 @@ export const mutations = {
|
|||||||
},
|
},
|
||||||
[types.default.ADD_ACCOUNT]: MutationHelpers.setSingleRecord,
|
[types.default.ADD_ACCOUNT]: MutationHelpers.setSingleRecord,
|
||||||
[types.default.EDIT_ACCOUNT]: MutationHelpers.update,
|
[types.default.EDIT_ACCOUNT]: MutationHelpers.update,
|
||||||
|
[types.default.SET_ACCOUNT_LIMITS]: MutationHelpers.updateAttributes,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -65,6 +65,7 @@ export default {
|
|||||||
|
|
||||||
// Agent
|
// Agent
|
||||||
SET_ACCOUNT_UI_FLAG: 'SET_ACCOUNT_UI_FLAG',
|
SET_ACCOUNT_UI_FLAG: 'SET_ACCOUNT_UI_FLAG',
|
||||||
|
SET_ACCOUNT_LIMITS: 'SET_ACCOUNT_LIMITS',
|
||||||
SET_ACCOUNTS: 'SET_ACCOUNTS',
|
SET_ACCOUNTS: 'SET_ACCOUNTS',
|
||||||
ADD_ACCOUNT: 'ADD_ACCOUNT',
|
ADD_ACCOUNT: 'ADD_ACCOUNT',
|
||||||
EDIT_ACCOUNT: 'EDIT_ACCOUNT',
|
EDIT_ACCOUNT: 'EDIT_ACCOUNT',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user