Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add mobile pricing plan #3509

Merged
merged 6 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/frontend-2/components/settings/workspaces/Billing.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<template>
<section>
<div class="md:mx-auto pb-6 md:pb-0">
<div class="md:max-w-5xl md:mx-auto pb-6 md:pb-0">
<SettingsSectionHeader title="Billing" text="Your workspace billing details" />
<template v-if="isBillingIntegrationEnabled">
<div class="flex flex-col gap-y-4 md:gap-y-6">
<BillingAlert v-if="workspaceResult" :workspace="workspaceResult.workspace" />
<SettingsSectionHeader title="Billing summary" subheading class="pt-4" />
<div class="border border-outline-3 rounded-lg">
<div
class="grid grid-cols-1 md:grid-cols-3 divide-y md:divide-y-0 md:divide-x"
class="grid grid-cols-1 md:grid-cols-3 divide-y divide-outline-3 md:divide-y-0 md:divide-x"
>
<div class="p-5 pt-4 flex flex-col gap-y-1">
<h3 class="text-body-xs text-foreground-2 pb-2">
Expand Down Expand Up @@ -77,6 +77,7 @@
class="pt-6"
:workspace-id="workspaceId"
:current-plan="currentPlan"
:is-admin="isAdmin"
/>
</div>
</template>
Expand All @@ -99,10 +100,12 @@ import {
import { useBillingActions } from '~/lib/billing/composables/actions'
import { pricingPlansConfig } from '~/lib/billing/helpers/constants'
import { Roles } from '@speckle/shared'

graphql(`
fragment SettingsWorkspacesBilling_Workspace on Workspace {
...BillingAlert_Workspace
id
role
plan {
...SettingsWorkspacesBillingPricingTable_WorkspacePlan
name
Expand Down Expand Up @@ -172,6 +175,9 @@ const nextPaymentDue = computed(() =>
: 'Never'
: dayjs().add(30, 'days').format('MMMM D, YYYY')
)
const isAdmin = computed(
() => workspaceResult.value?.workspace.role === Roles.Workspace.Admin
)

onMounted(() => {
const paymentStatusQuery = route.query?.payment_status
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<template>
<table class="w-full flex flex-col">
<thead>
<tr class="w-full flex">
<th class="w-1/4 flex pl-5 pr-6 pt-4 pb-2 font-medium">
<h4>Compare plans</h4>
</th>
<th
v-for="plan in plans"
:key="`desktop-${plan.name}`"
class="w-1/4 px-6 pt-4 pb-2"
:class="[
plan.name === WorkspacePlans.Team
? 'border border-b-0 border-outline-3 bg-foundation-2 rounded-t-lg'
: ''
]"
scope="col"
>
<SettingsWorkspacesBillingPricingTableHeader
:plan="plan"
:is-yearly-plan="isYearlyPlan"
:current-plan="currentPlan"
:workspace-id="workspaceId"
:is-admin="isAdmin"
/>
</th>
</tr>
</thead>
<tbody class="w-full flex flex-col">
<tr v-for="(feature, key, index) in features" :key="key" class="flex">
<th
class="font-normal text-foreground text-body-xs w-1/4 pr-3 pt-1"
scope="row"
>
<div class="border-b border-outline-3 min-h-[42px] pl-5 flex items-center">
{{ feature.name }}
</div>
</th>
<td
v-for="plan in plans"
:key="plan.name"
class="px-3 w-1/4 pt-1"
:class="[
plan.name === WorkspacePlans.Team
? 'border-l border-r border-outline-3 bg-foundation-2'
: '',
plan.name === WorkspacePlans.Team &&
index === Object.values(features).length - 1
? 'pb-6 border-b rounded-b-lg'
: ''
]"
>
<div class="border-b border-outline-3 flex items-center px-3 min-h-[42px]">
<CheckIcon
v-if="plan.features.includes(feature.name as PlanFeaturesList)"
class="w-3 h-3 text-foreground"
/>
</div>
</td>
</tr>
</tbody>
</table>
</template>

<script setup lang="ts">
import type { WorkspacePlan } from '~/lib/common/generated/gql/graphql'
import { WorkspacePlans } from '~/lib/common/generated/gql/graphql'
import { pricingPlansConfig } from '~/lib/billing/helpers/constants'
import type { PlanFeaturesList } from '~/lib/billing/helpers/types'
import { CheckIcon } from '@heroicons/vue/24/outline'
import type { MaybeNullOrUndefined } from '@speckle/shared'

defineProps<{
isYearlyPlan: boolean
currentPlan: MaybeNullOrUndefined<WorkspacePlan>
workspaceId: string
isAdmin: boolean
}>()

const plans = ref(pricingPlansConfig.plans)
const features = ref(pricingPlansConfig.features)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>
<div class="flex flex-col gap-y-1 font-normal">
<h4 class="text-foreground text-body-xs">
Workspace
<span class="capitalize">{{ plan.name }}</span>
</h4>
<p class="text-foreground text-heading">
£{{
isYearlyPlan
? plan.cost.yearly[Roles.Workspace.Member]
: plan.cost.monthly[Roles.Workspace.Member]
}}
per seat/month
</p>
<p class="text-foreground-2 text-body-2xs pt-1">
Billed {{ isYearlyPlan ? 'annually' : 'monthly' }}
</p>
<div class="w-full">
<FormButton
:color="plan.name === WorkspacePlans.Team ? 'primary' : 'outline'"
:disabled="(!hasTrialPlan && !canUpgradeToPlan) || !isAdmin"
class="mt-3"
full-width
@click="onUpgradePlanClick(plan.name)"
>
{{ hasTrialPlan ? 'Subscribe' : 'Upgrade' }} to&nbsp;
<span class="capitalize">{{ plan.name }}</span>
</FormButton>
</div>
</div>
</template>

<script setup lang="ts">
import { type PricingPlan } from '@/lib/billing/helpers/types'
import { Roles } from '@speckle/shared'
import {
type WorkspacePlan,
WorkspacePlanStatuses,
WorkspacePlans,
BillingInterval
} from '~/lib/common/generated/gql/graphql'
import { useBillingActions } from '~/lib/billing/composables/actions'
import type { MaybeNullOrUndefined } from '@speckle/shared'

const props = defineProps<{
plan: PricingPlan
isYearlyPlan: boolean
currentPlan: MaybeNullOrUndefined<WorkspacePlan>
workspaceId: string
isAdmin: boolean
}>()

const { upgradePlanRedirect } = useBillingActions()

const canUpgradeToPlan = computed(() => {
if (!props.currentPlan) return false

const allowedUpgrades: Record<WorkspacePlans, WorkspacePlans[]> = {
[WorkspacePlans.Team]: [WorkspacePlans.Pro, WorkspacePlans.Business],
[WorkspacePlans.Pro]: [WorkspacePlans.Business],
[WorkspacePlans.Business]: [],
[WorkspacePlans.Academia]: [],
[WorkspacePlans.Unlimited]: []
}

return allowedUpgrades[props.currentPlan.name].includes(props.plan.name)
})
const hasTrialPlan = computed(
() => props.currentPlan?.status === WorkspacePlanStatuses.Trial || !props.currentPlan
)

const onUpgradePlanClick = (plan: WorkspacePlans) => {
upgradePlanRedirect({
plan,
cycle: props.isYearlyPlan ? BillingInterval.Yearly : BillingInterval.Monthly,
workspaceId: props.workspaceId
})
}
</script>
Loading