feat: new billing page
This commit is contained in:
@@ -265,7 +265,7 @@
|
||||
"CAPTAIN": "Captain",
|
||||
"CAPTAIN_ASSISTANTS": "Assistants",
|
||||
"CAPTAIN_DOCUMENTS": "Documents",
|
||||
"CAPTAIN_RESPONSES" : "FAQs",
|
||||
"CAPTAIN_RESPONSES": "FAQs",
|
||||
"HOME": "Home",
|
||||
"AGENTS": "Agents",
|
||||
"AGENT_BOTS": "Bots",
|
||||
@@ -327,15 +327,26 @@
|
||||
},
|
||||
"BILLING_SETTINGS": {
|
||||
"TITLE": "Billing",
|
||||
"DESCRIPTION": "Manage your subscription here, upgrade your plan and get more for your team.",
|
||||
"CURRENT_PLAN": {
|
||||
"TITLE": "Current Plan",
|
||||
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses"
|
||||
"PLAN_NOTE": "You are currently subscribed to the **{plan}** plan with **{quantity}** licenses",
|
||||
"SEAT_COUNT": "Number of seats",
|
||||
"RENEWS_ON": "Renews on"
|
||||
},
|
||||
"VIEW_PRICING": "View Pricing",
|
||||
"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"
|
||||
},
|
||||
"CAPTAIN": {
|
||||
"TITLE": "Captain",
|
||||
"DESCRIPTION": "Manage usage and credits for Captain AI.",
|
||||
"BUTTON_TXT": "Buy more credits",
|
||||
"DOCUMENTS": "Documents",
|
||||
"RESPONSES": "Responses"
|
||||
},
|
||||
"CHAT_WITH_US": {
|
||||
"TITLE": "Need help?",
|
||||
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",
|
||||
|
||||
@@ -2,12 +2,19 @@
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store.js';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import BillingItem from './components/BillingItem.vue';
|
||||
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 { formatMessage } = useMessageFormatter();
|
||||
|
||||
const uiFlags = useMapGetter('accounts/getUIFlags');
|
||||
const store = useStore();
|
||||
@@ -31,6 +38,33 @@ 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');
|
||||
});
|
||||
|
||||
const captainLimits = computed(() => {
|
||||
return currentAccount.value?.limits?.captain;
|
||||
});
|
||||
|
||||
const documentLimits = computed(() => {
|
||||
if (captainLimits.value?.documents) {
|
||||
return useCamelCase(captainLimits.value.documents);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const responseLimits = computed(() => {
|
||||
if (captainLimits.value?.responses) {
|
||||
return useCamelCase(captainLimits.value.responses);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed property indicating if user has a billing plan
|
||||
* @returns {boolean}
|
||||
@@ -42,6 +76,7 @@ const hasABillingPlan = computed(() => {
|
||||
const fetchAccountDetails = async () => {
|
||||
if (!hasABillingPlan.value) {
|
||||
store.dispatch('accounts/subscription');
|
||||
store.dispatch('accounts/limits');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,66 +94,86 @@ onMounted(fetchAccountDetails);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 p-6 overflow-auto dark:bg-slate-900">
|
||||
<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="w-full">
|
||||
<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>
|
||||
<BillingItem
|
||||
:title="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.TITLE')"
|
||||
:description="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.DESCRIPTION')"
|
||||
:button-label="$t('BILLING_SETTINGS.MANAGE_SUBSCRIPTION.BUTTON_TXT')"
|
||||
@open="onClickBillingPortal"
|
||||
<SettingsLayout
|
||||
:is-loading="uiFlags.isFetchingItem"
|
||||
:loading-message="$t('ATTRIBUTES_MGMT.LOADING')"
|
||||
:no-records-found="!hasABillingPlan"
|
||||
:no-records-message="$t('BILLING_SETTINGS.NO_BILLING_USER')"
|
||||
class="max-w-2xl"
|
||||
>
|
||||
<template #header>
|
||||
<BaseSettingsHeader
|
||||
:title="$t('BILLING_SETTINGS.TITLE')"
|
||||
:description="$t('BILLING_SETTINGS.DESCRIPTION')"
|
||||
:link-text="$t('BILLING_SETTINGS.VIEW_PRICING')"
|
||||
feature-name="billing"
|
||||
/>
|
||||
<BillingItem
|
||||
: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"
|
||||
@open="onToggleChatWindow"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="grid grid-cols-3 gap-2 divide-x divide-n-weak">
|
||||
<DetailItem
|
||||
:label="$t('BILLING_SETTINGS.CURRENT_PLAN.TITLE')"
|
||||
:value="planName"
|
||||
/>
|
||||
<DetailItem
|
||||
:label="$t('BILLING_SETTINGS.CURRENT_PLAN.SEAT_COUNT')"
|
||||
:value="subscribedQuantity"
|
||||
/>
|
||||
<DetailItem
|
||||
:label="$t('BILLING_SETTINGS.CURRENT_PLAN.RENEWS_ON')"
|
||||
:value="subscriptionRenewsOn"
|
||||
/>
|
||||
</div>
|
||||
</BillingCard>
|
||||
<BillingCard
|
||||
: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>
|
||||
|
||||
<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>
|
||||
|
||||
<style lang="scss">
|
||||
.manage-subscription {
|
||||
@apply bg-white dark:bg-slate-800 flex justify-between mb-2 py-6 px-4 items-center rounded-md border border-solid border-slate-75 dark:border-slate-700;
|
||||
}
|
||||
|
||||
.current-plan--details {
|
||||
@apply border-b border-solid border-slate-75 dark:border-slate-800 mb-4 pb-4;
|
||||
|
||||
h6 {
|
||||
@apply text-slate-800 dark:text-slate-100;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply text-slate-600 dark:text-slate-200;
|
||||
}
|
||||
}
|
||||
|
||||
.manage-subscription {
|
||||
.manage-subscription--description {
|
||||
@apply mb-0 text-slate-600 dark:text-slate-200;
|
||||
}
|
||||
|
||||
h6 {
|
||||
@apply text-slate-800 dark:text-slate-100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
import SettingsContent from '../Wrapper.vue';
|
||||
import SettingsWrapper from '../SettingsWrapper.vue';
|
||||
import Index from './Index.vue';
|
||||
|
||||
export default {
|
||||
@@ -9,7 +9,7 @@ export default {
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
component: SettingsContent,
|
||||
component: SettingsWrapper,
|
||||
props: {
|
||||
headerTitle: 'BILLING_SETTINGS.TITLE',
|
||||
icon: 'credit-card-person',
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import BillingHeader from './BillingHeader.vue';
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="rounded-xl shadow-sm border border-n-weak bg-n-solid-3 py-5 space-y-5"
|
||||
>
|
||||
<BillingHeader :title :description class="px-5">
|
||||
<slot name="action" />
|
||||
</BillingHeader>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-[1fr_200px] gap-5">
|
||||
<div>
|
||||
<span class="text-base font-medium text-n-slate-12">
|
||||
{{ title }}
|
||||
</span>
|
||||
<p class="text-sm mt-1 text-n-slate-11">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
+2
-2
@@ -26,8 +26,8 @@ export default {
|
||||
<template>
|
||||
<div class="manage-subscription">
|
||||
<div>
|
||||
<h6>{{ title }}</h6>
|
||||
<p class="manage-subscription--description">
|
||||
<h6 class="text-n-slate-12">{{ title }}</h6>
|
||||
<p class="text-n-slate-11">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
consumed: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const percent = computed(() =>
|
||||
Math.round((props.consumed / props.totalCount) * 100)
|
||||
);
|
||||
|
||||
const colorClass = computed(() => {
|
||||
if (percent.value < 50) {
|
||||
return 'bg-n-teal-10';
|
||||
}
|
||||
if (percent.value < 80) {
|
||||
return 'bg-n-amber-10';
|
||||
}
|
||||
return 'bg-n-ruby-10';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex gap-5 items-center justify-between text-xs uppercase text-n-slate-10"
|
||||
>
|
||||
<div class="font-medium tracking-wider">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="tabular-nums">{{ consumed }} / {{ totalCount }}</div>
|
||||
</div>
|
||||
<div class="rounded-full overflow-hidden h-2 w-full bg-n-container mt-2">
|
||||
<div class="h-2" :class="colorClass" :style="{ width: `${percent}%` }" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-5">
|
||||
<span class="text-n-slate-11 text-xs">
|
||||
{{ label }}
|
||||
</span>
|
||||
<div class="mt-2 text-xl font-medium text-n-slate-12">
|
||||
{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user