diff --git a/packages/frontend-2/components/pricingTable/Plan.vue b/packages/frontend-2/components/pricingTable/Plan.vue
index a1d090349..e8a1774bf 100644
--- a/packages/frontend-2/components/pricingTable/Plan.vue
+++ b/packages/frontend-2/components/pricingTable/Plan.vue
@@ -5,8 +5,7 @@
- Workspace
- {{ plan }}
+ {{ formatName(plan) }}
{{ 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
isAdmin: boolean
activeBillingInterval: MaybeNullOrUndefined
+ hasSubscription: boolean
workspaceId: MaybeNullOrUndefined
}>()
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
+ })
}
}
diff --git a/packages/frontend-2/components/pricingTable/PricingTable.vue b/packages/frontend-2/components/pricingTable/PricingTable.vue
index 3a80c33ff..ff0131aca 100644
--- a/packages/frontend-2/components/pricingTable/PricingTable.vue
+++ b/packages/frontend-2/components/pricingTable/PricingTable.vue
@@ -9,6 +9,7 @@
:active-billing-interval="billingInterval"
:is-admin="isAdmin"
:workspace-id="props.workspaceId"
+ :has-subscription="!!subscription"
@on-yearly-interval-selected="onYearlyIntervalSelected"
/>
@@ -34,7 +35,11 @@ const props = defineProps<{
workspaceId: MaybeNullOrUndefined
}>()
-const { billingInterval, plan: currentPlan } = useWorkspacePlan(props.slug)
+const {
+ billingInterval,
+ plan: currentPlan,
+ subscription
+} = useWorkspacePlan(props.slug)
const isYearlySelected = ref(false)
diff --git a/packages/frontend-2/components/settings/workspaces/billing/Page.vue b/packages/frontend-2/components/settings/workspaces/billing/Page.vue
index 8643e9bbb..0d992f3a8 100644
--- a/packages/frontend-2/components/settings/workspaces/billing/Page.vue
+++ b/packages/frontend-2/components/settings/workspaces/billing/Page.vue
@@ -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 {
diff --git a/packages/frontend-2/components/settings/workspaces/billing/PricingTable/Plan.vue b/packages/frontend-2/components/settings/workspaces/billing/PricingTable/Plan.vue
index dd7c9f56f..d14a9ed83 100644
--- a/packages/frontend-2/components/settings/workspaces/billing/PricingTable/Plan.vue
+++ b/packages/frontend-2/components/settings/workspaces/billing/PricingTable/Plan.vue
@@ -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
diff --git a/packages/frontend-2/components/settings/workspaces/billing/Summary.vue b/packages/frontend-2/components/settings/workspaces/billing/Summary.vue
index ad3f1b9b8..89955aaf3 100644
--- a/packages/frontend-2/components/settings/workspaces/billing/Summary.vue
+++ b/packages/frontend-2/components/settings/workspaces/billing/Summary.vue
@@ -7,7 +7,7 @@
Current plan
- {{ plan?.name }}
+ {{ formatName(plan?.name) }}
@@ -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
diff --git a/packages/frontend-2/components/settings/workspaces/billing/UpgradeDialog.vue b/packages/frontend-2/components/settings/workspaces/billing/UpgradeDialog.vue
index 58647bf8c..b51575834 100644
--- a/packages/frontend-2/components/settings/workspaces/billing/UpgradeDialog.vue
+++ b/packages/frontend-2/components/settings/workspaces/billing/UpgradeDialog.vue
@@ -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
diff --git a/packages/frontend-2/lib/billing/helpers/plan.ts b/packages/frontend-2/lib/billing/helpers/plan.ts
new file mode 100644
index 000000000..404a43689
--- /dev/null
+++ b/packages/frontend-2/lib/billing/helpers/plan.ts
@@ -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.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]
+}
diff --git a/packages/frontend-2/lib/billing/helpers/prices.ts b/packages/frontend-2/lib/billing/helpers/prices.ts
deleted file mode 100644
index 961d2efe6..000000000
--- a/packages/frontend-2/lib/billing/helpers/prices.ts
+++ /dev/null
@@ -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)
- }`
-}
diff --git a/packages/frontend-2/lib/workspaces/composables/plan.ts b/packages/frontend-2/lib/workspaces/composables/plan.ts
index a54460a0d..993848a9d 100644
--- a/packages/frontend-2/lib/workspaces/composables/plan.ts
+++ b/packages/frontend-2/lib/workspaces/composables/plan.ts
@@ -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
}
}
diff --git a/packages/shared/src/workspaces/helpers/plans.ts b/packages/shared/src/workspaces/helpers/plans.ts
index 1e5050751..481f0c40f 100644
--- a/packages/shared/src/workspaces/helpers/plans.ts
+++ b/packages/shared/src/workspaces/helpers/plans.ts
@@ -38,12 +38,12 @@ export type PaidWorkspacePlans =
export const UnpaidWorkspacePlans = {
// Old
- Unlimited: 'unlimited',
- Academia: 'academia',
StarterInvoiced: 'starterInvoiced',
PlusInvoiced: 'plusInvoiced',
BusinessInvoiced: 'businessInvoiced',
// New
+ Unlimited: 'unlimited',
+ Academia: 'academia',
Free: 'free'
}