Files
leadchat/app/javascript/dashboard/routes/dashboard/settings/billing/Index.vue
Shivam Mishra ef7bf66476 feat: Add frontend changes for Captain limits (#10749)
This PR introduces several improvements to the Captain AI dashboard
section:

- New billing page, with new colors, layout and meters for Captain usage
- Updated the base paywall component to use new colors
- Updated PageLayout.vue, it's more generic and can be used for other
pages as well
   - Use flags to toggle empty state and loading state
- Add prop for `featureFlag` to show the paywall slot based on feature
enabled on account
- Update `useAccount` to add a `isCloudFeatureEnabled`
- **Removed feature flag checks from captain route definitions**, so the
captain entry will always be visible on the sidebar
- Add banner to Captain pages for the following cases
   - Responses usage is over 80%
   - Documents limit is fully exhausted


### Screenshots

<details><summary>Free plan</summary>
<p>

![CleanShot 2025-01-22 at 18 37
11@2x](https://github.com/user-attachments/assets/17d3ddba-9095-4e81-9b6f-45b5f69e6a3f)
![CleanShot 2025-01-22 at 18 37
04@2x](https://github.com/user-attachments/assets/df9bb0a6-085f-45da-97d4-74cbcc33fc7e)


</p>
</details> 

<details><summary>Paid plan</summary>
<p>

![CleanShot 2025-01-22 at 18 36
45@2x](https://github.com/user-attachments/assets/a7ccf9d4-143b-49e4-8149-83c7a7985023)

![CleanShot 2025-01-22 at 20 23
57@2x](https://github.com/user-attachments/assets/c6ce35ba-e537-486d-85c8-4cc2d4e76438)


</p>
</details>

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-01-24 09:21:09 -08:00

182 lines
5.5 KiB
Vue

<script setup>
import { computed, onMounted } from 'vue';
import { useMapGetter, useStore } from 'dashboard/composables/store.js';
import { useAccount } from 'dashboard/composables/useAccount';
import { useCaptain } from 'dashboard/composables/useCaptain';
import { format } from 'date-fns';
import BillingMeter from './components/BillingMeter.vue';
import BillingCard from './components/BillingCard.vue';
import BillingHeader from './components/BillingHeader.vue';
import DetailItem from './components/DetailItem.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import SettingsLayout from '../SettingsLayout.vue';
import ButtonV4 from 'next/button/Button.vue';
const { currentAccount } = useAccount();
const {
captainEnabled,
captainLimits,
documentLimits,
responseLimits,
fetchLimits,
} = useCaptain();
const uiFlags = useMapGetter('accounts/getUIFlags');
const store = useStore();
const customAttributes = computed(() => {
return currentAccount.value.custom_attributes || {};
});
/**
* Computed property for plan name
* @returns {string|undefined}
*/
const planName = computed(() => {
return customAttributes.value.plan_name;
});
/**
* Computed property for subscribed quantity
* @returns {number|undefined}
*/
const subscribedQuantity = computed(() => {
return customAttributes.value.subscribed_quantity;
});
const subscriptionRenewsOn = computed(() => {
if (!customAttributes.value.subscription_ends_on) return '';
const endDate = new Date(customAttributes.value.subscription_ends_on);
// return date as 12 Jan, 2034
return format(endDate, 'dd MMM, yyyy');
});
/**
* Computed property indicating if user has a billing plan
* @returns {boolean}
*/
const hasABillingPlan = computed(() => {
return !!planName.value;
});
const fetchAccountDetails = async () => {
if (!hasABillingPlan.value) {
store.dispatch('accounts/subscription');
fetchLimits();
}
};
const onClickBillingPortal = () => {
store.dispatch('accounts/checkout');
};
const onToggleChatWindow = () => {
if (window.$chatwoot) {
window.$chatwoot.toggle();
}
};
onMounted(fetchAccountDetails);
</script>
<template>
<SettingsLayout
:is-loading="uiFlags.isFetchingItem"
:loading-message="$t('ATTRIBUTES_MGMT.LOADING')"
:no-records-found="!hasABillingPlan"
:no-records-message="$t('BILLING_SETTINGS.NO_BILLING_USER')"
>
<template #header>
<BaseSettingsHeader
:title="$t('BILLING_SETTINGS.TITLE')"
:description="$t('BILLING_SETTINGS.DESCRIPTION')"
:link-text="$t('BILLING_SETTINGS.VIEW_PRICING')"
feature-name="billing"
/>
</template>
<template #body>
<section class="grid gap-4">
<BillingCard
:title="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.TITLE')"
:description="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.DESCRIPTION')"
>
<template #action>
<ButtonV4 sm solid blue @click="onClickBillingPortal">
{{ $t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.BUTTON_TXT') }}
</ButtonV4>
</template>
<div
v-if="planName || subscribedQuantity || subscriptionRenewsOn"
class="grid lg:grid-cols-4 sm:grid-cols-3 grid-cols-1 gap-2 divide-x divide-n-weak"
>
<DetailItem
:label="$t('BILLING_SETTINGS.CURRENT_PLAN.TITLE')"
:value="planName"
/>
<DetailItem
v-if="subscribedQuantity"
:label="$t('BILLING_SETTINGS.CURRENT_PLAN.SEAT_COUNT')"
:value="subscribedQuantity"
/>
<DetailItem
v-if="subscriptionRenewsOn"
:label="$t('BILLING_SETTINGS.CURRENT_PLAN.RENEWS_ON')"
:value="subscriptionRenewsOn"
/>
</div>
</BillingCard>
<BillingCard
v-if="captainEnabled"
:title="$t('BILLING_SETTINGS.CAPTAIN.TITLE')"
:description="$t('BILLING_SETTINGS.CAPTAIN.DESCRIPTION')"
>
<template #action>
<ButtonV4 sm solid slate disabled>
{{ $t('BILLING_SETTINGS.CAPTAIN.BUTTON_TXT') }}
</ButtonV4>
</template>
<div v-if="captainLimits && responseLimits" class="px-5">
<BillingMeter
:title="$t('BILLING_SETTINGS.CAPTAIN.RESPONSES')"
v-bind="responseLimits"
/>
</div>
<div v-if="captainLimits && documentLimits" class="px-5">
<BillingMeter
:title="$t('BILLING_SETTINGS.CAPTAIN.DOCUMENTS')"
v-bind="documentLimits"
/>
</div>
</BillingCard>
<BillingCard
v-else
:title="$t('BILLING_SETTINGS.CAPTAIN.TITLE')"
:description="$t('BILLING_SETTINGS.CAPTAIN.UPGRADE')"
>
<template #action>
<ButtonV4 sm solid slate @click="onClickBillingPortal">
{{ $t('CAPTAIN.PAYWALL.UPGRADE_NOW') }}
</ButtonV4>
</template>
</BillingCard>
<BillingHeader
class="px-1 mt-5"
:title="$t('BILLING_SETTINGS.CHAT_WITH_US.TITLE')"
:description="$t('BILLING_SETTINGS.CHAT_WITH_US.DESCRIPTION')"
>
<ButtonV4
sm
solid
slate
icon="i-lucide-life-buoy"
@open="onToggleChatWindow"
>
{{ $t('BILLING_SETTINGS.CHAT_WITH_US.BUTTON_TXT') }}
</ButtonV4>
</BillingHeader>
</section>
</template>
</SettingsLayout>
</template>