feat: add upgrade banner for SLA feature (#9240)
- Add an upgrade CTA for the SLA feature ------------------- Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
@@ -142,20 +142,13 @@ const settings = accountId => ({
|
|||||||
toStateName: 'settings_applications',
|
toStateName: 'settings_applications',
|
||||||
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
|
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: 'credit-card-person',
|
|
||||||
label: 'BILLING',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/billing`),
|
|
||||||
toStateName: 'billing_settings_index',
|
|
||||||
showOnlyOnCloud: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: 'key',
|
icon: 'key',
|
||||||
label: 'AUDIT_LOGS',
|
label: 'AUDIT_LOGS',
|
||||||
hasSubMenu: false,
|
hasSubMenu: false,
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/audit-log/list`),
|
toState: frontendURL(`accounts/${accountId}/settings/audit-log/list`),
|
||||||
toStateName: 'auditlogs_list',
|
toStateName: 'auditlogs_list',
|
||||||
|
isEnterpriseOnly: true,
|
||||||
featureFlag: FEATURE_FLAGS.AUDIT_LOGS,
|
featureFlag: FEATURE_FLAGS.AUDIT_LOGS,
|
||||||
beta: true,
|
beta: true,
|
||||||
},
|
},
|
||||||
@@ -165,9 +158,18 @@ const settings = accountId => ({
|
|||||||
hasSubMenu: false,
|
hasSubMenu: false,
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/sla/list`),
|
toState: frontendURL(`accounts/${accountId}/settings/sla/list`),
|
||||||
toStateName: 'sla_list',
|
toStateName: 'sla_list',
|
||||||
|
isEnterpriseOnly: true,
|
||||||
featureFlag: FEATURE_FLAGS.SLA,
|
featureFlag: FEATURE_FLAGS.SLA,
|
||||||
beta: true,
|
beta: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'credit-card-person',
|
||||||
|
label: 'BILLING',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/billing`),
|
||||||
|
toStateName: 'billing_settings_index',
|
||||||
|
showOnlyOnCloud: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<li v-show="isMenuItemVisible" class="mt-1">
|
<li v-show="isMenuItemVisible" class="mt-1">
|
||||||
<div v-if="hasSubMenu" class="flex justify-between">
|
<div v-if="hasSubMenu" class="flex justify-between">
|
||||||
<span
|
<span
|
||||||
class="text-sm text-slate-700 dark:text-slate-200 font-semibold my-2 px-2 pt-1"
|
class="px-2 pt-1 my-2 text-sm font-semibold text-slate-700 dark:text-slate-200"
|
||||||
>
|
>
|
||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
v-else
|
v-else
|
||||||
class="rounded-lg leading-4 font-medium flex items-center p-2 m-0 text-sm text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
|
class="flex items-center p-2 m-0 text-sm font-medium leading-4 rounded-lg text-slate-700 dark:text-slate-100 hover:bg-slate-25 dark:hover:bg-slate-800"
|
||||||
:class="computedClass"
|
:class="computedClass"
|
||||||
:to="menuItem && menuItem.toState"
|
:to="menuItem && menuItem.toState"
|
||||||
>
|
>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
<span
|
<span
|
||||||
v-if="showChildCount(menuItem.count)"
|
v-if="showChildCount(menuItem.count)"
|
||||||
class="rounded-md text-xxs font-medium mx-1 py-0 px-1"
|
class="px-1 py-0 mx-1 font-medium rounded-md text-xxs"
|
||||||
:class="{
|
:class="{
|
||||||
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
|
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
|
||||||
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
|
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
|
||||||
@@ -46,13 +46,13 @@
|
|||||||
v-if="menuItem.beta"
|
v-if="menuItem.beta"
|
||||||
data-view-component="true"
|
data-view-component="true"
|
||||||
label="Beta"
|
label="Beta"
|
||||||
class="px-1 mx-1 inline-block font-medium leading-4 border border-green-400 text-green-500 rounded-lg text-xxs"
|
class="inline-block px-1 mx-1 font-medium leading-4 text-green-500 border border-green-400 rounded-lg text-xxs"
|
||||||
>
|
>
|
||||||
{{ $t('SIDEBAR.BETA') }}
|
{{ $t('SIDEBAR.BETA') }}
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<ul v-if="hasSubMenu" class="list-none ml-0 mb-0">
|
<ul v-if="hasSubMenu" class="mb-0 ml-0 list-none">
|
||||||
<secondary-child-nav-item
|
<secondary-child-nav-item
|
||||||
v-for="child in menuItem.children"
|
v-for="child in menuItem.children"
|
||||||
:key="child.id"
|
:key="child.id"
|
||||||
@@ -94,6 +94,7 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
import adminMixin from '../../../mixins/isAdmin';
|
import adminMixin from '../../../mixins/isAdmin';
|
||||||
|
import configMixin from 'shared/mixins/configMixin';
|
||||||
import {
|
import {
|
||||||
getInboxClassByType,
|
getInboxClassByType,
|
||||||
getInboxWarningIconClass,
|
getInboxWarningIconClass,
|
||||||
@@ -107,7 +108,7 @@ import {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { SecondaryChildNavItem },
|
components: { SecondaryChildNavItem },
|
||||||
mixins: [adminMixin],
|
mixins: [adminMixin, configMixin],
|
||||||
props: {
|
props: {
|
||||||
menuItem: {
|
menuItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -132,15 +133,33 @@ export default {
|
|||||||
},
|
},
|
||||||
isMenuItemVisible() {
|
isMenuItemVisible() {
|
||||||
if (this.menuItem.globalConfigFlag) {
|
if (this.menuItem.globalConfigFlag) {
|
||||||
|
// this checks for the `csmlEditorHost` flag in the global config
|
||||||
|
// if this is present, we toggle the CSML editor menu item
|
||||||
|
// TODO: This is very specific, and can be handled better, fix it
|
||||||
return !!this.globalConfig[this.menuItem.globalConfigFlag];
|
return !!this.globalConfig[this.menuItem.globalConfigFlag];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isFeatureEnabled = true;
|
||||||
|
if (this.menuItem.featureFlag) {
|
||||||
|
isFeatureEnabled = this.isFeatureEnabledonAccount(
|
||||||
|
this.accountId,
|
||||||
|
this.menuItem.featureFlag
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.menuItem.isEnterpriseOnly) {
|
||||||
|
if (!this.isEnterprise) return false;
|
||||||
|
return isFeatureEnabled || this.globalConfig.displayManifest;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.menuItem.featureFlag) {
|
if (this.menuItem.featureFlag) {
|
||||||
return this.isFeatureEnabledonAccount(
|
return this.isFeatureEnabledonAccount(
|
||||||
this.accountId,
|
this.accountId,
|
||||||
this.menuItem.featureFlag
|
this.menuItem.featureFlag
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return isFeatureEnabled;
|
||||||
},
|
},
|
||||||
isAllConversations() {
|
isAllConversations() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,6 +6,17 @@
|
|||||||
"DESCRIPTION": "Service Level Agreements (SLAs) are contracts that define clear expectations between your team and customers. They establish standards for response and resolution times, creating a framework for accountability and ensures a consistent, high-quality experience.",
|
"DESCRIPTION": "Service Level Agreements (SLAs) are contracts that define clear expectations between your team and customers. They establish standards for response and resolution times, creating a framework for accountability and ensures a consistent, high-quality experience.",
|
||||||
"LEARN_MORE": "Learn more about SLA",
|
"LEARN_MORE": "Learn more about SLA",
|
||||||
"LOADING": "Fetching SLAs",
|
"LOADING": "Fetching SLAs",
|
||||||
|
"PAYWALL": {
|
||||||
|
"TITLE": "Upgrade to create SLAs",
|
||||||
|
"AVAILABLE_ON": "The SLA feature is only available in the Business and Enterprise plans.",
|
||||||
|
"UPGRADE_PROMPT": "Upgrade your plan to get access to advanced features like team management, automations, custom attributes, and more.",
|
||||||
|
"UPGRADE_NOW": "Upgrade now"
|
||||||
|
},
|
||||||
|
"ENTERPRISE_PAYWALL": {
|
||||||
|
"AVAILABLE_ON": "The SLA feature is only available in the paid plans.",
|
||||||
|
"UPGRADE_PROMPT": "Upgrade to a paid plan to access advanced features like audit logs, agent capacity, and more.",
|
||||||
|
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||||||
|
},
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "There are no SLAs available in this account.",
|
"404": "There are no SLAs available in this account.",
|
||||||
"EMPTY": {
|
"EMPTY": {
|
||||||
|
|||||||
@@ -10,44 +10,17 @@
|
|||||||
<SLAListItemLoading v-for="ii in 2" :key="ii" class="mb-3" />
|
<SLAListItemLoading v-for="ii in 2" :key="ii" class="mb-3" />
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div v-if="!records.length" class="w-full min-h-[12rem] relative">
|
<SLAPaywallEnterprise
|
||||||
<div class="w-full space-y-3">
|
v-if="isBehindAPaywall"
|
||||||
<SLA-list-item
|
:is-super-admin="isSuperAdmin"
|
||||||
class="opacity-25 dark:opacity-20"
|
:is-on-chatwoot-cloud="isOnChatwootCloud"
|
||||||
:sla-name="$t('SLA.LIST.EMPTY.TITLE_1')"
|
@click="onClickCTA"
|
||||||
:description="$t('SLA.LIST.EMPTY.DESC_1')"
|
/>
|
||||||
first-response="20m"
|
<SLAEmptyState
|
||||||
next-response="1h"
|
v-else-if="!records.length"
|
||||||
resolution-time="24h"
|
@primary-action="openAddPopup"
|
||||||
has-business-hours
|
/>
|
||||||
/>
|
<div v-else class="flex flex-col w-full h-full gap-3">
|
||||||
<SLA-list-item
|
|
||||||
class="opacity-25 dark:opacity-20"
|
|
||||||
:sla-name="$t('SLA.LIST.EMPTY.TITLE_2')"
|
|
||||||
:description="$t('SLA.LIST.EMPTY.DESC_2')"
|
|
||||||
first-response="2h"
|
|
||||||
next-response="4h"
|
|
||||||
resolution-time="4d"
|
|
||||||
has-business-hours
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 flex flex-col items-center justify-center w-full h-full bg-gradient-to-t from-white dark:from-slate-900 to-transparent"
|
|
||||||
>
|
|
||||||
<p class="max-w-xs text-sm font-medium text-center">
|
|
||||||
{{ $t('SLA.LIST.404') }}
|
|
||||||
</p>
|
|
||||||
<woot-button
|
|
||||||
color-scheme="primary"
|
|
||||||
icon="plus-sign"
|
|
||||||
class="px-5 mt-4 rounded-xl"
|
|
||||||
@click="openAddPopup"
|
|
||||||
>
|
|
||||||
{{ $t('SLA.ADD_ACTION_LONG') }}
|
|
||||||
</woot-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="records.length" class="flex flex-col w-full h-full gap-3">
|
|
||||||
<SLA-list-item
|
<SLA-list-item
|
||||||
v-for="sla in records"
|
v-for="sla in records"
|
||||||
:key="sla.title"
|
:key="sla.title"
|
||||||
@@ -80,25 +53,30 @@
|
|||||||
</settings-layout>
|
</settings-layout>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import AddSLA from './AddSLA.vue';
|
||||||
import SettingsLayout from '../SettingsLayout.vue';
|
import SettingsLayout from '../SettingsLayout.vue';
|
||||||
|
import SLAEmptyState from './components/SLAEmptyState.vue';
|
||||||
import SLAHeader from './components/SLAHeader.vue';
|
import SLAHeader from './components/SLAHeader.vue';
|
||||||
import SLAListItem from './components/SLAListItem.vue';
|
import SLAListItem from './components/SLAListItem.vue';
|
||||||
import SLAListItemLoading from './components/SLAListItemLoading.vue';
|
import SLAListItemLoading from './components/SLAListItemLoading.vue';
|
||||||
|
import SLAPaywallEnterprise from './components/SLAPaywallEnterprise.vue';
|
||||||
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { convertSecondsToTimeUnit } from '@chatwoot/utils';
|
import { convertSecondsToTimeUnit } from '@chatwoot/utils';
|
||||||
|
|
||||||
import AddSLA from './AddSLA.vue';
|
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import configMixin from 'shared/mixins/configMixin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AddSLA,
|
AddSLA,
|
||||||
|
SettingsLayout,
|
||||||
|
SLAEmptyState,
|
||||||
SLAHeader,
|
SLAHeader,
|
||||||
SLAListItem,
|
SLAListItem,
|
||||||
SLAListItemLoading,
|
SLAListItemLoading,
|
||||||
SettingsLayout,
|
SLAPaywallEnterprise,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin],
|
mixins: [alertMixin, configMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: {},
|
loading: {},
|
||||||
@@ -109,7 +87,12 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
globalConfig: 'globalConfig/get',
|
||||||
|
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
|
||||||
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
records: 'sla/getSLA',
|
records: 'sla/getSLA',
|
||||||
|
currentUser: 'getCurrentUser',
|
||||||
|
accountId: 'getCurrentAccountId',
|
||||||
uiFlags: 'sla/getUIFlags',
|
uiFlags: 'sla/getUIFlags',
|
||||||
}),
|
}),
|
||||||
deleteConfirmText() {
|
deleteConfirmText() {
|
||||||
@@ -121,12 +104,21 @@ export default {
|
|||||||
deleteMessage() {
|
deleteMessage() {
|
||||||
return ` ${this.selectedResponse.name}`;
|
return ` ${this.selectedResponse.name}`;
|
||||||
},
|
},
|
||||||
|
isBehindAPaywall() {
|
||||||
|
return !this.isFeatureEnabledonAccount(this.accountId, 'sla');
|
||||||
|
},
|
||||||
|
isSuperAdmin() {
|
||||||
|
return this.currentUser.type === 'SuperAdmin';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('sla/get');
|
this.$store.dispatch('sla/get');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openAddPopup() {
|
openAddPopup() {
|
||||||
|
if (this.isBehindAPaywall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.showAddPopup = true;
|
this.showAddPopup = true;
|
||||||
},
|
},
|
||||||
hideAddPopup() {
|
hideAddPopup() {
|
||||||
@@ -166,6 +158,12 @@ export default {
|
|||||||
if (!time) return '-';
|
if (!time) return '-';
|
||||||
return `${time}${unit}`;
|
return `${time}${unit}`;
|
||||||
},
|
},
|
||||||
|
onClickCTA() {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'billing_settings_index',
|
||||||
|
params: { accountId: this.accountId },
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup>
|
||||||
|
import SLAListItem from './SLAListItem.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full min-h-[12rem] relative">
|
||||||
|
<div class="w-full space-y-3">
|
||||||
|
<SLA-list-item
|
||||||
|
class="opacity-25 dark:opacity-20"
|
||||||
|
:sla-name="$t('SLA.LIST.EMPTY.TITLE_1')"
|
||||||
|
:description="$t('SLA.LIST.EMPTY.DESC_1')"
|
||||||
|
first-response="20m"
|
||||||
|
next-response="1h"
|
||||||
|
resolution-time="24h"
|
||||||
|
has-business-hours
|
||||||
|
/>
|
||||||
|
<SLA-list-item
|
||||||
|
class="opacity-25 dark:opacity-20"
|
||||||
|
:sla-name="$t('SLA.LIST.EMPTY.TITLE_2')"
|
||||||
|
:description="$t('SLA.LIST.EMPTY.DESC_2')"
|
||||||
|
first-response="2h"
|
||||||
|
next-response="4h"
|
||||||
|
resolution-time="4d"
|
||||||
|
has-business-hours
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 flex flex-col items-center justify-center w-full h-full bg-gradient-to-t from-white dark:from-slate-900 to-transparent"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup>
|
||||||
|
import BaseEmptyState from './BaseEmptyState.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['primary-action']);
|
||||||
|
const primaryAction = () => emit('primary-action');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<base-empty-state>
|
||||||
|
<p class="max-w-xs text-sm font-medium text-center">
|
||||||
|
{{ $t('SLA.LIST.404') }}
|
||||||
|
</p>
|
||||||
|
<woot-button
|
||||||
|
color-scheme="primary"
|
||||||
|
icon="plus-sign"
|
||||||
|
class="px-5 mt-4 rounded-xl"
|
||||||
|
@click="primaryAction"
|
||||||
|
>
|
||||||
|
{{ $t('SLA.ADD_ACTION_LONG') }}
|
||||||
|
</woot-button>
|
||||||
|
</base-empty-state>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup>
|
||||||
|
import BaseEmptyState from './BaseEmptyState.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isSuperAdmin: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isOnChatwootCloud: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['click']);
|
||||||
|
const i18nKey = props.isOnChatwootCloud ? 'PAYWALL' : 'ENTERPRISE_PAYWALL';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<base-empty-state>
|
||||||
|
<div
|
||||||
|
class="flex flex-col max-w-md px-6 py-6 bg-white border shadow dark:bg-slate-800 rounded-xl border-slate-100 dark:border-slate-900"
|
||||||
|
>
|
||||||
|
<div class="flex items-center w-full gap-2 mb-4">
|
||||||
|
<span
|
||||||
|
class="flex items-center justify-center w-6 h-6 rounded-full bg-woot-75/70 dark:bg-woot-800/40"
|
||||||
|
>
|
||||||
|
<fluent-icon
|
||||||
|
size="14"
|
||||||
|
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
|
||||||
|
icon="lock-closed"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span class="text-base font-medium text-slate-900 dark:text-white">
|
||||||
|
{{ $t('SLA.PAYWALL.TITLE') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="text-sm font-normal"
|
||||||
|
v-html="$t(`SLA.${i18nKey}.AVAILABLE_ON`)"
|
||||||
|
/>
|
||||||
|
<p class="text-sm font-normal">
|
||||||
|
{{ $t(`SLA.${i18nKey}.UPGRADE_PROMPT`) }}
|
||||||
|
<span v-if="!isOnChatwootCloud && !isSuperAdmin">
|
||||||
|
{{ $t('SLA.ENTERPRISE_PAYWALL.ASK_ADMIN') }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<template v-if="isOnChatwootCloud">
|
||||||
|
<woot-button
|
||||||
|
color-scheme="primary"
|
||||||
|
class="w-full mt-2 text-center rounded-xl"
|
||||||
|
size="expanded"
|
||||||
|
is-expanded
|
||||||
|
@click="emit('click')"
|
||||||
|
>
|
||||||
|
{{ $t('SLA.PAYWALL.UPGRADE_NOW') }}
|
||||||
|
</woot-button>
|
||||||
|
</template>
|
||||||
|
<template v-if="isSuperAdmin">
|
||||||
|
<a href="/super_admin" class="block w-full">
|
||||||
|
<woot-button
|
||||||
|
color-scheme="primary"
|
||||||
|
class="w-full mt-2 text-center rounded-xl"
|
||||||
|
size="expanded"
|
||||||
|
is-expanded
|
||||||
|
>
|
||||||
|
{{ $t('SLA.PAYWALL.UPGRADE_NOW') }}
|
||||||
|
</woot-button>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</base-empty-state>
|
||||||
|
</template>
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||||
import * as types from '../mutation-types';
|
import * as types from '../mutation-types';
|
||||||
import AccountAPI from '../../api/account';
|
import AccountAPI from '../../api/account';
|
||||||
|
import { differenceInDays } from 'date-fns';
|
||||||
import EnterpriseAccountAPI from '../../api/enterprise/account';
|
import EnterpriseAccountAPI from '../../api/enterprise/account';
|
||||||
import { throwErrorMessage } from '../utils/api';
|
import { throwErrorMessage } from '../utils/api';
|
||||||
|
|
||||||
const findRecordById = ($state, id) =>
|
const findRecordById = ($state, id) =>
|
||||||
$state.records.find(record => record.id === Number(id)) || {};
|
$state.records.find(record => record.id === Number(id)) || {};
|
||||||
|
|
||||||
|
const TRIAL_PERIOD_DAYS = 15;
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
records: [],
|
records: [],
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
@@ -19,26 +22,19 @@ const state = {
|
|||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getAccount: $state => id => {
|
getAccount: $state => id => {
|
||||||
return $state.records.find(record => record.id === Number(id)) || {};
|
return findRecordById($state, id);
|
||||||
},
|
},
|
||||||
getUIFlags($state) {
|
getUIFlags($state) {
|
||||||
return $state.uiFlags;
|
return $state.uiFlags;
|
||||||
},
|
},
|
||||||
isFeatureEnabledonAccount:
|
isTrialAccount: $state => id => {
|
||||||
($state, _, __, rootGetters) => (id, featureName) => {
|
const account = findRecordById($state, id);
|
||||||
// If a user is SuperAdmin and has access to the account, then they would see all the available features
|
const createdAt = new Date(account.created_at);
|
||||||
const isUserASuperAdmin =
|
const diffDays = differenceInDays(new Date(), createdAt);
|
||||||
rootGetters.getCurrentUser?.type === 'SuperAdmin';
|
|
||||||
if (isUserASuperAdmin) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { features = {} } = findRecordById($state, id);
|
return diffDays <= TRIAL_PERIOD_DAYS;
|
||||||
|
},
|
||||||
return features[featureName] || false;
|
isFeatureEnabledonAccount: $state => (id, featureName) => {
|
||||||
},
|
|
||||||
// There are some features which can be enabled/disabled globally
|
|
||||||
isFeatureEnabledGlobally: $state => (id, featureName) => {
|
|
||||||
const { features = {} } = findRecordById($state, id);
|
const { features = {} } = findRecordById($state, id);
|
||||||
return features[featureName] || false;
|
return features[featureName] || false;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -52,13 +52,4 @@ describe('#getters', () => {
|
|||||||
)(1, 'auto_resolve_conversations')
|
)(1, 'auto_resolve_conversations')
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isFeatureEnabledGlobally', () => {
|
|
||||||
const state = {
|
|
||||||
records: [accountData],
|
|
||||||
};
|
|
||||||
expect(
|
|
||||||
getters.isFeatureEnabledGlobally(state)(1, 'auto_resolve_conversations')
|
|
||||||
).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,5 +12,9 @@ export default {
|
|||||||
isEnterprise() {
|
isEnterprise() {
|
||||||
return window.chatwootConfig.isEnterprise === 'true';
|
return window.chatwootConfig.isEnterprise === 'true';
|
||||||
},
|
},
|
||||||
|
enterprisePlanName() {
|
||||||
|
// returns "community" or "enterprise"
|
||||||
|
return window.chatwootConfig?.enterprisePlanName;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,6 +40,9 @@
|
|||||||
fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>',
|
fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>',
|
||||||
signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>',
|
signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>',
|
||||||
isEnterprise: '<%= @global_config['IS_ENTERPRISE'] %>',
|
isEnterprise: '<%= @global_config['IS_ENTERPRISE'] %>',
|
||||||
|
<% if @global_config['IS_ENTERPRISE'] %>
|
||||||
|
enterprisePlanName: '<%= @global_config['INSTALLATION_PRICING_PLAN'] %>',
|
||||||
|
<% end %>
|
||||||
<% if @global_config['VAPID_PUBLIC_KEY'] %>
|
<% if @global_config['VAPID_PUBLIC_KEY'] %>
|
||||||
vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(@global_config['VAPID_PUBLIC_KEY']).bytes %>),
|
vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(@global_config['VAPID_PUBLIC_KEY']).bytes %>),
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
Reference in New Issue
Block a user