feat: Allow SaaS users to manage subscription within the dashboard (#5059)

This commit is contained in:
Pranav Raj S
2022-07-19 19:04:17 +05:30
committed by GitHub
parent 0cee42a9f9
commit 7fc0d166e8
33 changed files with 773 additions and 18 deletions

View File

@@ -0,0 +1,18 @@
/* global axios */
import ApiClient from '../ApiClient';
class EnterpriseAccountAPI extends ApiClient {
constructor() {
super('', { accountScoped: true, enterprise: true });
}
checkout() {
return axios.post(`${this.url}checkout`);
}
subscription() {
return axios.post(`${this.url}subscription`);
}
}
export default new EnterpriseAccountAPI();

View File

@@ -0,0 +1,31 @@
import accountAPI from '../account';
import ApiClient from '../../ApiClient';
import describeWithAPIMock from '../../specs/apiSpecHelper';
describe('#enterpriseAccountAPI', () => {
it('creates correct instance', () => {
expect(accountAPI).toBeInstanceOf(ApiClient);
expect(accountAPI).toHaveProperty('get');
expect(accountAPI).toHaveProperty('show');
expect(accountAPI).toHaveProperty('create');
expect(accountAPI).toHaveProperty('update');
expect(accountAPI).toHaveProperty('delete');
expect(accountAPI).toHaveProperty('checkout');
});
describeWithAPIMock('API calls', context => {
it('#checkout', () => {
accountAPI.checkout();
expect(context.axiosMock.post).toHaveBeenCalledWith(
'/enterprise/api/v1/checkout'
);
});
it('#subscription', () => {
accountAPI.subscription();
expect(context.axiosMock.post).toHaveBeenCalledWith(
'/enterprise/api/v1/subscription'
);
});
});
});

View File

@@ -19,6 +19,7 @@
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
@@ -67,6 +68,7 @@ export default {
...mapGetters({
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
inboxes: 'inboxes/getInboxes',
accountId: 'getCurrentAccountId',
currentRole: 'getCurrentRole',

View File

@@ -30,6 +30,7 @@ const settings = accountId => ({
'settings_teams_edit',
'settings_teams_edit_members',
'settings_teams_edit_finish',
'billing_settings_index',
'automation_list',
],
menuItems: [
@@ -100,6 +101,14 @@ const settings = accountId => ({
toState: frontendURL(`accounts/${accountId}/settings/applications`),
toStateName: 'settings_applications',
},
{
icon: 'credit-card-person',
label: 'BILLING',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/billing`),
toStateName: 'billing_settings_index',
showOnlyOnCloud: true,
},
{
icon: 'settings',
label: 'ACCOUNT_SETTINGS',

View File

@@ -55,6 +55,10 @@ export default {
type: String,
default: '',
},
isOnChatwootCloud: {
type: Boolean,
default: false,
},
},
computed: {
hasSecondaryMenu() {
@@ -67,12 +71,18 @@ export default {
if (!this.currentRole) {
return [];
}
return this.menuConfig.menuItems.filter(
const menuItemsFilteredByRole = this.menuConfig.menuItems.filter(
menuItem =>
window.roleWiseRoutes[this.currentRole].indexOf(
menuItem.toStateName
) > -1
);
return menuItemsFilteredByRole.filter(item => {
if (item.showOnlyOnCloud) {
return this.isOnChatwootCloud;
}
return true;
});
},
inboxSection() {
return {

View File

@@ -167,6 +167,7 @@
"CUSTOM_ATTRIBUTES": "Custom Attributes",
"AUTOMATION": "Automation",
"TEAMS": "Teams",
"BILLING": "Billing",
"CUSTOM_VIEWS_FOLDER": "Folders",
"CUSTOM_VIEWS_SEGMENTS": "Segments",
"ALL_CONTACTS": "All Contacts",
@@ -195,6 +196,25 @@
"CATEGORY": "Category"
}
},
"BILLING_SETTINGS": {
"TITLE": "Billing",
"CURRENT_PLAN" : {
"TITLE": "Current Plan",
"PLAN_NOTE": "You are currently subscribed to the **%{plan}** plan with **%{quantity}** licenses"
},
"MANAGE_SUBSCRIPTION": {
"TITLE": "Manage your subscription",
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
"BUTTON_TXT": "Go to the billing portal"
},
"CHAT_WITH_US": {
"TITLE": "Need help?",
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",
"BUTTON_TXT": "Chat with us"
},
"NO_BILLING_USER": "Your billing account is being configured. Please refresh the page and try again."
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",
"NEW_ACCOUNT": "New Account",

View File

@@ -0,0 +1,116 @@
<template>
<div class="columns profile--settings">
<woot-loading-state v-if="uiFlags.isFetchingItem" />
<div v-else-if="!hasABillingPlan">
<p>{{ $t('BILLING_SETTINGS.NO_BILLING_USER') }}</p>
</div>
<div v-else class="small-12 columns ">
<div class="current-plan--details">
<h6>{{ $t('BILLING_SETTINGS.CURRENT_PLAN.TITLE') }}</h6>
<div
v-dompurify-html="
formatMessage(
$t('BILLING_SETTINGS.CURRENT_PLAN.PLAN_NOTE', {
plan: planName,
quantity: subscribedQuantity,
})
)
"
/>
</div>
<billing-item
:title="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.TITLE')"
:description="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.DESCRIPTION')"
:button-label="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.BUTTON_TXT')"
@click="onClickBillingPortal"
/>
<billing-item
:title="$t('BILLING_SETTINGS.CHAT_WITH_US.TITLE')"
:description="$t('BILLING_SETTINGS.CHAT_WITH_US.DESCRIPTION')"
:button-label="$t('BILLING_SETTINGS.CHAT_WITH_US.BUTTON_TXT')"
button-icon="chat-multiple"
@click="onToggleChatWindow"
/>
</div>
</div>
</template>
<script>
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import accountMixin from '../../../../mixins/account';
import BillingItem from './components/BillingItem.vue';
export default {
components: { BillingItem },
mixins: [accountMixin, alertMixin, messageFormatterMixin],
computed: {
...mapGetters({
getAccount: 'accounts/getAccount',
uiFlags: 'accounts/getUIFlags',
}),
currentAccount() {
return this.getAccount(this.accountId) || {};
},
customAttributes() {
return this.currentAccount.custom_attributes || {};
},
hasABillingPlan() {
return !!this.planName;
},
planName() {
return this.customAttributes.plan_name || '';
},
subscribedQuantity() {
return this.customAttributes.subscribed_quantity || 0;
},
},
mounted() {
this.fetchAccountDetails();
},
methods: {
async fetchAccountDetails() {
await this.$store.dispatch('accounts/get');
if (!this.hasABillingPlan) {
this.$store.dispatch('accounts/subscription');
}
},
onClickBillingPortal() {
this.$store.dispatch('accounts/checkout');
},
onToggleChatWindow() {
if (window.$chatwoot) {
window.$chatwoot.toggle();
}
},
},
};
</script>
<style lang="scss">
.manage-subscription {
align-items: center;
background: var(--white);
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
display: flex;
justify-content: space-between;
margin-bottom: var(--space-small);
padding: var(--space-medium) var(--space-normal);
}
.current-plan--details {
border-bottom: 1px solid var(--color-border);
margin-bottom: var(--space-normal);
padding-bottom: var(--space-normal);
}
.manage-subscription {
.manage-subscription--description {
margin-bottom: 0;
}
}
</style>

View File

@@ -0,0 +1,26 @@
import SettingsContent from '../Wrapper';
import Index from './Index.vue';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/billing'),
roles: ['administrator'],
component: SettingsContent,
props: {
headerTitle: 'BILLING_SETTINGS.TITLE',
icon: 'credit-card-person',
showNewButton: false,
},
children: [
{
path: '',
name: 'billing_settings_index',
component: Index,
roles: ['administrator'],
},
],
},
],
};

View File

@@ -0,0 +1,38 @@
<template>
<div class="manage-subscription">
<div>
<h6>{{ title }}</h6>
<p class="manage-subscription--description">
{{ description }}
</p>
</div>
<div>
<woot-button variant="smooth" :icon="buttonIcon" @click="$emit('click')">
{{ buttonLabel }}
</woot-button>
</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: '',
},
description: {
type: String,
default: '',
},
buttonLabel: {
type: String,
default: '',
},
buttonIcon: {
type: String,
default: 'edit',
},
},
};
</script>

View File

@@ -13,6 +13,7 @@ import teams from './teams/teams.routes';
import attributes from './attributes/attributes.routes';
import automation from './automation/automation.routes';
import store from '../../../store';
import billing from './billing/billing.routes';
export default {
routes: [
@@ -29,16 +30,17 @@ export default {
},
...account.routes,
...agent.routes,
...attributes.routes,
...automation.routes,
...billing.routes,
...campaigns.routes,
...canned.routes,
...inbox.routes,
...integrationapps.routes,
...integrations.routes,
...labels.routes,
...profile.routes,
...reports.routes,
...teams.routes,
...campaigns.routes,
...integrationapps.routes,
...attributes.routes,
...automation.routes,
],
};

View File

@@ -1,9 +1,8 @@
/* eslint no-console: 0 */
/* eslint no-param-reassign: 0 */
/* eslint no-shadow: 0 */
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
import * as types from '../mutation-types';
import AccountAPI from '../../api/account';
import EnterpriseAccountAPI from '../../api/enterprise/account';
import { throwErrorMessage } from '../utils/api';
const state = {
records: [],
@@ -11,6 +10,7 @@ const state = {
isFetching: false,
isFetchingItem: false,
isUpdating: false,
isCheckoutInProcess: false,
},
};
@@ -60,6 +60,29 @@ export const actions = {
throw error;
}
},
checkout: async ({ commit }) => {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isCheckoutInProcess: true });
try {
const response = await EnterpriseAccountAPI.checkout();
window.location = response.data.redirect_url;
} catch (error) {
throwErrorMessage(error);
} finally {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isCheckoutInProcess: false });
}
},
subscription: async ({ commit }) => {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isCheckoutInProcess: true });
try {
await EnterpriseAccountAPI.subscription();
} catch (error) {
throwErrorMessage(error);
} finally {
commit(types.default.SET_ACCOUNT_UI_FLAG, { isCheckoutInProcess: false });
}
},
};
export const mutations = {

View File

@@ -63,6 +63,7 @@
"M10 13.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5z",
"M4 6h1v6.5A2.5 2.5 0 0 0 7.5 15H14v1a2 2 0 0 1-2 2H5.5A3.5 3.5 0 0 1 2 14.5V8a2 2 0 0 1 2-2z"
],
"credit-card-person-outline": "M2 7.25A3.25 3.25 0 0 1 5.25 4h13.5A3.25 3.25 0 0 1 22 7.25V10h-.258A3.74 3.74 0 0 0 20.5 7.455V7.25a1.75 1.75 0 0 0-1.75-1.75H5.25A1.75 1.75 0 0 0 3.5 7.25v.25h11.95c-.44.409-.782.922-.987 1.5H3.5v5.75c0 .966.784 1.75 1.75 1.75h6.78c.06.522.217 1.028.458 1.5H5.25A3.25 3.25 0 0 1 2 14.75v-7.5Zm21 8.25a1.5 1.5 0 0 0-1.5-1.5h-7a1.5 1.5 0 0 0-1.5 1.5v.5c0 1.971 1.86 4 5 4 3.14 0 5-2.029 5-4v-.5Zm-2.25-5.25a2.75 2.75 0 1 0-5.5 0 2.75 2.75 0 0 0 5.5 0Z",
"delete-outline": "M12 1.75a3.25 3.25 0 0 1 3.245 3.066L15.25 5h5.25a.75.75 0 0 1 .102 1.493L20.5 6.5h-.796l-1.28 13.02a2.75 2.75 0 0 1-2.561 2.474l-.176.006H8.313a2.75 2.75 0 0 1-2.714-2.307l-.023-.174L4.295 6.5H3.5a.75.75 0 0 1-.743-.648L2.75 5.75a.75.75 0 0 1 .648-.743L3.5 5h5.25A3.25 3.25 0 0 1 12 1.75Zm6.197 4.75H5.802l1.267 12.872a1.25 1.25 0 0 0 1.117 1.122l.127.006h7.374c.6 0 1.109-.425 1.225-1.002l.02-.126L18.196 6.5ZM13.75 9.25a.75.75 0 0 1 .743.648L14.5 10v7a.75.75 0 0 1-1.493.102L13 17v-7a.75.75 0 0 1 .75-.75Zm-3.5 0a.75.75 0 0 1 .743.648L11 10v7a.75.75 0 0 1-1.493.102L9.5 17v-7a.75.75 0 0 1 .75-.75Zm1.75-6a1.75 1.75 0 0 0-1.744 1.606L10.25 5h3.5A1.75 1.75 0 0 0 12 3.25Z",
"dismiss-circle-outline": "M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2Zm0 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm3.446 4.897.084.073a.75.75 0 0 1 .073.976l-.073.084L13.061 12l2.47 2.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-2.47 2.47a.75.75 0 0 1-.976.072l-.084-.073a.75.75 0 0 1-.073-.976l.073-.084L10.939 12l-2.47-2.47a.75.75 0 0 1-.072-.976l.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l2.47-2.47a.75.75 0 0 1 .976-.072Z",
"dismiss-outline": "m4.397 4.554.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l6.47-6.47a.75.75 0 1 1 1.06 1.061L13.061 12l6.47 6.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-6.47 6.47a.75.75 0 0 1-1.06-1.061L10.939 12l-6.47-6.47a.75.75 0 0 1-.072-.976l.073-.084-.073.084Z",

View File

@@ -15,6 +15,7 @@ const {
TERMS_URL: termsURL,
WIDGET_BRAND_URL: widgetBrandURL,
DISABLE_USER_PROFILE_UPDATE: disableUserProfileUpdate,
DEPLOYMENT_ENV: deploymentEnv,
} = window.globalConfig || {};
const state = {
@@ -23,6 +24,7 @@ const state = {
appVersion,
brandName,
chatwootInboxToken,
deploymentEnv,
createNewAccountFromDashboard,
directUploadsEnabled: directUploadsEnabled === 'true',
disableUserProfileUpdate: disableUserProfileUpdate === 'true',
@@ -38,6 +40,7 @@ const state = {
export const getters = {
get: $state => $state,
isOnChatwootCloud: $state => $state.deploymentEnv === 'cloud',
};
export const actions = {};