seitime-frappe/billing/src/pages/Plans.vue
2024-11-22 22:31:36 +05:30

232 lines
5.5 KiB
Vue

<template>
<header class="flex h-10.5 border-b items-center justify-between py-2 px-5 shrink-0">
<Breadcrumbs :items="[{ label: 'Plans' }]" />
</header>
<div class="flex flex-col overflow-hidden px-60 pt-6">
<ListView
v-if="rows.length"
:columns="columns"
:rows="rows"
row-key="name"
:options="{
selectable: false,
showTooltip: false,
}"
>
<ListHeader />
<ListRows>
<ListRow
v-for="row in rows"
:key="row.name"
v-slot="{ column, item }"
:row="row"
:class="{ 'bg-gray-50 rounded': row.isCurrent }"
>
<ListRowItem :item="item" :align="column.align">
<Badge
v-if="column.key == 'upgrade' && row.isCurrent"
class="shrink-0 bg-white"
label="Current plan"
variant="outline"
size="lg"
/>
<Button
v-else-if="column.key == 'upgrade' && !row.isCurrent"
:label="row.downgrade ? 'Downgrade' : 'Upgrade'"
@click="row.onClick"
:disabled="!row.downgradable && row.downgrade"
/>
<div
v-if="column.key == 'price'"
class="text-base text-gray-900 font-semibold"
>
<span v-if="row.isTrial" class=""> Free trial </span>
<span v-else>
<span>{{ item.currency }} {{ item.label }}</span>
<span class="text-gray-700 font-normal">/mo</span>
</span>
</div>
<Tooltip v-if="column.key == 'info'">
<template #body>
<PlanDetails :plan="item" />
</template>
<FeatherIcon class="h-4 cursor-pointer" name="info" />
</Tooltip>
</ListRowItem>
</ListRow>
</ListRows>
</ListView>
<div v-else class="flex flex-1 items-center justify-center">
<Spinner class="size-8" />
</div>
<UpgradePlanStepsModal
v-if="showUpgradePlanStepsModal"
v-model="showUpgradePlanStepsModal"
:defaultStep="defaultStep"
:plan="plan"
@success="() => emit('success')"
/>
</div>
</template>
<script setup>
import PlanDetails from '@/components/PlanDetails.vue'
import UpgradePlanStepsModal from '@/components/UpgradePlanStepsModal.vue'
import {
ListView,
ListHeader,
ListRows,
ListRow,
ListRowItem,
Badge,
Spinner,
Button,
FeatherIcon,
Tooltip,
createResource,
Breadcrumbs,
} from 'frappe-ui'
import { createDialog } from '@/dialogs'
import { parseSize, ConfirmMessage } from '@/utils'
import { ref, computed, provide, inject, markRaw, h } from 'vue'
const emit = defineEmits(['success'])
const team = inject('team')
const currentSiteInfo = inject('currentSiteInfo')
const billingDetails = createResource({
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
params: { method: 'billing.get_information' },
cache: 'billingDetails',
auto: true,
})
const plans = createResource({
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
params: { method: 'site.get_plans' },
cache: 'plans',
auto: true,
})
const currentPlan = computed(() => {
if (!currentSiteInfo.data) return null
return currentSiteInfo.data.plan?.name || 'Trial'
})
const currency = computed(() => {
if (!team.data) return 'INR'
return team.data.currency || 'INR'
})
const columns = [
{
label: '',
key: 'info',
width: '8px',
},
{
label: 'Cost',
key: 'price',
width: 0.8,
},
{
label: 'CPU',
key: 'cpu',
width: 1.2,
},
{
label: 'Memory',
key: 'memory',
width: 1.2,
},
{
label: 'Disk',
key: 'disk',
width: 0.7,
},
{
label: '',
key: 'upgrade',
width: 0.8,
align: 'right',
},
]
const rows = computed(() => {
if (!currentPlan.value) return []
if (!plans.data) return []
let currentPlanIndex = plans.data.findIndex((plan) => plan.name === currentPlan.value)
return plans.data
.map((plan, i) => {
let cpu = plan.cpu_time_per_day > 1 ? 'compute hrs/day' : 'compute hr/day'
let price = currency.value === 'INR' ? plan.price_inr : plan.price_usd
return {
name: plan.name,
price: {
label: price.toString(),
currency: currency.value === 'INR' ? '₹' : '$',
},
cpu: `${plan.cpu_time_per_day} ${cpu}`,
memory: `${parseSize(plan.max_database_usage)} Database`,
disk: `${parseSize(plan.max_storage_usage)} Disk`,
info: plan,
isCurrent: plan.name === currentPlan.value,
isTrial: plan.is_trial_plan,
downgradable: plan.allow_downgrading_from_other_plan,
downgrade: currentPlanIndex > i,
onClick: () => changePlan(plan, price.toString()),
}
})
.filter((row) => !row.isTrial || (row.isTrial && row.name === currentPlan.value))
})
const defaultStep = ref(1)
const showUpgradePlanStepsModal = ref(false)
const plan = ref({
name: '',
price: '',
currency: '',
})
function changePlan(_plan, price) {
if (!billingDetails.data?.country || !team.data.payment_mode) {
defaultStep.value = billingDetails.data.country ? 2 : 1
showUpgradePlanStepsModal.value = true
plan.value = {
name: _plan.name,
price,
currency: currency.value === 'INR' ? '₹' : '$',
}
return
}
createDialog({
title: 'Change plan',
component: markRaw(
h(ConfirmMessage, { price, currency: currency.value === 'INR' ? '₹' : '$' })
),
actions: [
{
label: 'Change plan',
variant: 'solid',
onClick: (close) => changePlanRequest(_plan.name, close),
},
],
})
}
function changePlanRequest(planName, close) {
createResource({
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
params: { method: 'site.change_plan', data: { plan: planName } },
auto: true,
onSuccess: () => {
currentSiteInfo.reload()
plans.reload()
close()
},
})
}
provide('plans', plans)
</script>