Files
speckle-server/packages/frontend-2/components/billing/Alert.vue
T

161 lines
4.9 KiB
Vue

<template>
<div>
<template v-if="!hasValidPlan">
<div
v-if="condensed"
class="flex items-center justify-between rounded-md p-2 pl-3 text-body-3xs font-medium bg-info-lighter text-primary-focus gap-x-2"
>
{{ title }}
<FormButton
v-if="actions.length > 0"
size="sm"
:disabled="actions[0].disabled"
@click="actions[0].onClick"
>
{{ actions[0].title }}
</FormButton>
</div>
<CommonAlert v-else :color="alertColor" :actions="actions">
<template #title>
{{ title }}
</template>
<template #description>
{{ description }}
</template>
</CommonAlert>
</template>
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import { graphql } from '~/lib/common/generated/gql'
import {
type BillingAlert_WorkspaceFragment,
WorkspacePlanStatuses
} from '~/lib/common/generated/gql/graphql'
import { useBillingActions } from '~/lib/billing/composables/actions'
import type { AlertAction, AlertColor } from '@speckle/ui-components'
graphql(`
fragment BillingAlert_Workspace on Workspace {
id
plan {
name
status
createdAt
}
subscription {
billingInterval
currentBillingCycleEnd
}
}
`)
const props = defineProps<{
workspace: BillingAlert_WorkspaceFragment
actions?: Array<AlertAction>
condensed?: boolean
}>()
const { billingPortalRedirect } = useBillingActions()
const planStatus = computed(() => props.workspace.plan?.status)
// If there is no plan status, we assume it's a trial
const isTrial = computed(
() => !planStatus.value || planStatus.value === WorkspacePlanStatuses.Trial
)
const isPaymentFailed = computed(
() => planStatus.value === WorkspacePlanStatuses.PaymentFailed
)
const isScheduledForCancelation = computed(
() => planStatus.value === WorkspacePlanStatuses.CancelationScheduled
)
const trialDaysLeft = computed(() => {
const createdAt = props.workspace.plan?.createdAt
const trialEndDate = dayjs(createdAt).add(31, 'days')
const diffDays = trialEndDate.diff(dayjs(), 'day')
return Math.max(0, diffDays)
})
const title = computed(() => {
if (isTrial.value) {
if (trialDaysLeft.value === 0) {
return 'Final day of free trial'
}
if (props.condensed) {
return `${trialDaysLeft.value} day${
trialDaysLeft.value !== 1 ? 's' : ''
} left in trial`
} else
return `You have ${trialDaysLeft.value} day${
trialDaysLeft.value !== 1 ? 's' : ''
} left on your free trial`
}
switch (planStatus.value) {
case WorkspacePlanStatuses.CancelationScheduled:
return `Your workspace subscription is scheduled for cancellation`
case WorkspacePlanStatuses.Canceled:
return `Your workspace subscription has been cancelled`
case WorkspacePlanStatuses.Expired:
return `Your free trial has ended`
case WorkspacePlanStatuses.PaymentFailed:
return "Your last payment didn't go through"
default:
return ''
}
})
const description = computed(() => {
if (isTrial.value) {
return trialDaysLeft.value === 0
? 'Upgrade to a paid plan to continue using your workspace'
: 'Upgrade to a paid plan to start your subscription'
}
switch (planStatus.value) {
case WorkspacePlanStatuses.CancelationScheduled:
return 'Once the current billing cycle ends your workspace will enter read-only mode. Renew your subscription to undo.'
case WorkspacePlanStatuses.Canceled:
return 'Your workspace has been cancelled and is in read-only mode. Subscribe to a plan to regain full access.'
case WorkspacePlanStatuses.Expired:
return "The workspace is in a read-only locked state until there's an active subscription. Subscribe to a plan to regain full access."
case WorkspacePlanStatuses.PaymentFailed:
return "Update your payment information now to ensure your workspace doesn't go into maintenance mode."
default:
return ''
}
})
const hasValidPlan = computed(() => planStatus.value === WorkspacePlanStatuses.Valid)
const alertColor = computed<AlertColor>(() => {
switch (planStatus.value) {
case WorkspacePlanStatuses.PaymentFailed:
case WorkspacePlanStatuses.Canceled:
return 'danger'
case WorkspacePlanStatuses.CancelationScheduled:
case WorkspacePlanStatuses.Expired:
return 'warning'
default:
return 'neutral'
}
})
const actions = computed((): AlertAction[] => {
const actions: Array<AlertAction> = props.actions ?? []
if (isPaymentFailed.value) {
actions.push({
title: 'Update payment information',
onClick: () => billingPortalRedirect(props.workspace.id),
disabled: !props.workspace.id
})
} else if (isScheduledForCancelation.value) {
actions.push({
title: 'Renew subscription',
onClick: () => billingPortalRedirect(props.workspace.id),
disabled: !props.workspace.id
})
}
return actions
})
</script>