Fix: Various pricing plan fixes (#4244)

This commit is contained in:
Mike
2025-03-21 21:49:12 +01:00
committed by GitHub
parent 56e79b0dd8
commit 751aece587
10 changed files with 65 additions and 28 deletions
@@ -5,8 +5,7 @@
<div class="lg:h-32">
<div class="flex items-center gap-x-2">
<h4 class="text-body font-medium">
Workspace
<span class="capitalize">{{ plan }}</span>
{{ formatName(plan) }}
</h4>
<CommonBadge v-if="badgeText" rounded>
{{ badgeText }}
@@ -92,10 +91,9 @@ import {
WorkspacePlanStatuses,
BillingInterval
} from '~/lib/common/generated/gql/graphql'
import { startCase } from 'lodash'
import { XMarkIcon } from '@heroicons/vue/24/outline'
import { useWorkspacePlanPrices } from '~/lib/billing/composables/prices'
import { formatPrice } from '~/lib/billing/helpers/prices'
import { formatPrice, formatName } from '~/lib/billing/helpers/plan'
import { useBillingActions } from '~/lib/billing/composables/actions'
defineEmits<{
@@ -108,11 +106,12 @@ const props = defineProps<{
currentPlan: MaybeNullOrUndefined<WorkspacePlan>
isAdmin: boolean
activeBillingInterval: MaybeNullOrUndefined<BillingInterval>
hasSubscription: boolean
workspaceId: MaybeNullOrUndefined<string>
}>()
const { pricesNew } = useWorkspacePlanPrices()
const { upgradePlan } = useBillingActions()
const { upgradePlan, redirectToCheckout } = useBillingActions()
const isYearlyIntervalSelected = ref(props.yearlyIntervalSelected)
@@ -204,19 +203,20 @@ const isSelectable = computed(() => {
})
const buttonColor = computed(() => {
if (props.currentPlan?.status === WorkspacePlanStatuses.Expired) {
return props.plan === WorkspacePlans.Starter ? 'primary' : 'outline'
if (props.currentPlan?.name === WorkspacePlans.Free) {
return props.plan === WorkspacePlans.Pro ? 'primary' : 'outline'
}
return 'outline'
})
const buttonText = computed(() => {
// Allow selection during trial, expired or canceled state
// Allow if current plan is Free, or the current plan is expired/canceled
if (
props.currentPlan?.name === WorkspacePlans.Free ||
props.currentPlan?.status === WorkspacePlanStatuses.Expired ||
props.currentPlan?.status === WorkspacePlanStatuses.Canceled
) {
return `Subscribe to ${startCase(props.plan)}`
return `Subscribe to ${formatName(props.plan)}`
}
// Current plan case
if (isCurrentPlan.value) {
@@ -234,7 +234,7 @@ const buttonText = computed(() => {
return 'Change to annual plan'
}
// Upgrade case
return canUpgradeToPlan.value ? `Upgrade to ${startCase(props.plan)}` : ''
return canUpgradeToPlan.value ? `Upgrade to ${formatName(props.plan)}` : ''
})
const badgeText = computed(() =>
@@ -243,7 +243,9 @@ const badgeText = computed(() =>
const handleUpgradeClick = () => {
if (!props.workspaceId) return
if (props.plan === WorkspacePlans.Team || props.plan === WorkspacePlans.Pro) {
if (props.plan !== WorkspacePlans.Team && props.plan !== WorkspacePlans.Pro) return
if (props.hasSubscription) {
upgradePlan({
plan: props.plan,
cycle: isYearlyIntervalSelected.value
@@ -251,6 +253,14 @@ const handleUpgradeClick = () => {
: BillingInterval.Monthly,
workspaceId: props.workspaceId
})
} else {
redirectToCheckout({
plan: props.plan,
cycle: isYearlyIntervalSelected.value
? BillingInterval.Yearly
: BillingInterval.Monthly,
workspaceId: props.workspaceId
})
}
}
@@ -9,6 +9,7 @@
:active-billing-interval="billingInterval"
:is-admin="isAdmin"
:workspace-id="props.workspaceId"
:has-subscription="!!subscription"
@on-yearly-interval-selected="onYearlyIntervalSelected"
/>
</div>
@@ -34,7 +35,11 @@ const props = defineProps<{
workspaceId: MaybeNullOrUndefined<string>
}>()
const { billingInterval, plan: currentPlan } = useWorkspacePlan(props.slug)
const {
billingInterval,
plan: currentPlan,
subscription
} = useWorkspacePlan(props.slug)
const isYearlySelected = ref(false)
@@ -229,7 +229,7 @@ import { useMixpanel } from '~/lib/core/composables/mp'
import { guideBillingUrl } from '~/lib/common/helpers/route'
import { adminUpdateWorkspacePlanMutation } from '~/lib/billing/graphql/mutations'
import { useWorkspacePlanPrices } from '~/lib/billing/composables/prices'
import { formatPrice } from '~/lib/billing/helpers/prices'
import { formatPrice } from '~/lib/billing/helpers/plan'
graphql(`
fragment SettingsWorkspacesBilling_Workspace on Workspace {
@@ -120,7 +120,7 @@ import { startCase, isFunction } from 'lodash'
import { XMarkIcon } from '@heroicons/vue/24/outline'
import type { SetupContext } from 'vue'
import { useWorkspacePlanPrices } from '~/lib/billing/composables/prices'
import { formatPrice } from '~/lib/billing/helpers/prices'
import { formatPrice } from '~/lib/billing/helpers/plan'
const emit = defineEmits<{
(e: 'onYearlyIntervalSelected', value: boolean): void
@@ -7,7 +7,7 @@
<div class="p-5 pt-4 flex flex-col">
<h3 class="text-body-xs text-foreground-2 pb-4">Current plan</h3>
<p class="text-heading-lg text-foreground capitalize">
{{ plan?.name }}
{{ formatName(plan?.name) }}
</p>
</div>
@@ -62,6 +62,7 @@
import { useWorkspacePlan } from '~~/lib/workspaces/composables/plan'
import { useBillingActions } from '~/lib/billing/composables/actions'
import type { MaybeNullOrUndefined } from '@speckle/shared'
import { formatName } from '~/lib/billing/helpers/plan'
defineProps<{
workspaceId?: MaybeNullOrUndefined<string>
@@ -31,7 +31,7 @@ import type { PaidWorkspacePlansOld } from '@speckle/shared'
import { Roles } from '@speckle/shared'
import { isPaidPlan } from '~/lib/billing/helpers/types'
import { useWorkspacePlanPrices } from '~/lib/billing/composables/prices'
import { formatPrice } from '~/lib/billing/helpers/prices'
import { formatPrice } from '~/lib/billing/helpers/plan'
const props = defineProps<{
plan: PaidWorkspacePlansOld
@@ -0,0 +1,29 @@
import { isInteger } from 'lodash-es'
import { WorkspacePlans } from '@speckle/shared'
export const formatPrice = (price?: { amount: number; currencySymbol: string }) => {
if (!price) return ''
return `${price.currencySymbol}${
isInteger(price.amount) ? price.amount : price.amount.toFixed(2)
}`
}
// Internal plan names dont match the names we use in the product
export const formatName = (plan?: WorkspacePlans) => {
if (!plan) return ''
const formattedPlanNames: Record<WorkspacePlans, string> = {
[WorkspacePlans.Unlimited]: 'Unlimited',
[WorkspacePlans.Academia]: 'Academia',
[WorkspacePlans.StarterInvoiced]: 'Starter (invoiced)',
[WorkspacePlans.PlusInvoiced]: 'Plus (Invoiced)',
[WorkspacePlans.BusinessInvoiced]: 'Business (Invoiced)',
[WorkspacePlans.Starter]: 'Starter',
[WorkspacePlans.Plus]: 'Plus',
[WorkspacePlans.Business]: 'Business',
[WorkspacePlans.Free]: 'Free',
[WorkspacePlans.Team]: 'Starter',
[WorkspacePlans.Pro]: 'Business'
}
return formattedPlanNames[plan]
}
@@ -1,8 +0,0 @@
import { isInteger } from 'lodash-es'
export const formatPrice = (price?: { amount: number; currencySymbol: string }) => {
if (!price) return ''
return `${price.currencySymbol}${
isInteger(price.amount) ? price.amount : price.amount.toFixed(2)
}`
}
@@ -78,7 +78,6 @@ export const useWorkspacePlan = (slug: string) => {
const intervalIsYearly = computed(
() => billingInterval.value === BillingInterval.Yearly
)
// TODO: Replace with value from API call, this a placeholder value
const seatPrice = 15
@@ -109,6 +108,7 @@ export const useWorkspacePlan = (slug: string) => {
billingInterval,
intervalIsYearly,
totalCostFormatted,
statusIsCancelationScheduled
statusIsCancelationScheduled,
subscription
}
}
@@ -38,12 +38,12 @@ export type PaidWorkspacePlans =
export const UnpaidWorkspacePlans = <const>{
// Old
Unlimited: 'unlimited',
Academia: 'academia',
StarterInvoiced: 'starterInvoiced',
PlusInvoiced: 'plusInvoiced',
BusinessInvoiced: 'businessInvoiced',
// New
Unlimited: 'unlimited',
Academia: 'academia',
Free: 'free'
}