Feat: Update add-on cards (#4382)
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
<!-- TODO: Implement final values, links and copy -->
|
||||
<template>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
<CommonCard class="flex flex-col gap-y-3 p-6">
|
||||
<h3 class="text-body font-medium">Unlimited projects and models</h3>
|
||||
<p class="text-foreground-2 text-body-xs flex-1 mb-3">
|
||||
Add more projects and models to your workspace.
|
||||
</p>
|
||||
<FormButton color="outline" size="sm">Contact us</FormButton>
|
||||
</CommonCard>
|
||||
|
||||
<CommonCard class="flex flex-col gap-y-3 p-6">
|
||||
<h3 class="text-body font-medium">Extra data regions</h3>
|
||||
<p class="text-foreground-2 text-body-xs flex-1 mb-3">
|
||||
Access to almost all data residency regions.
|
||||
</p>
|
||||
<FormButton v-if="planIsBusiness" color="outline" size="sm">
|
||||
Contact us
|
||||
</FormButton>
|
||||
<p v-else class="font-medium text-body-xs">Available only on Business plan</p>
|
||||
</CommonCard>
|
||||
|
||||
<CommonCard class="flex flex-col gap-y-3 p-6">
|
||||
<h3 class="text-body font-medium">Priority support</h3>
|
||||
<p class="text-foreground-2 text-body-xs flex-1 mb-3">
|
||||
Private support channel for your workspace.
|
||||
</p>
|
||||
<FormButton v-if="planIsBusiness" color="outline" size="sm">
|
||||
Contact us
|
||||
</FormButton>
|
||||
<p v-else class="font-medium text-body-xs">Available only on Business plan</p>
|
||||
</CommonCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useWorkspacePlan } from '~~/lib/workspaces/composables/plan'
|
||||
import { PaidWorkspacePlansNew } from '@speckle/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
slug: string
|
||||
}>()
|
||||
|
||||
const { plan } = useWorkspacePlan(props.slug)
|
||||
|
||||
const planIsBusiness = computed(() => plan.value?.name === PaidWorkspacePlansNew.Pro)
|
||||
</script>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
<SettingsWorkspacesBillingAddOnsCard
|
||||
title="Unlimited projects and models"
|
||||
info="Add unlimited projects and models to your workspace."
|
||||
disclaimer="Only on Starter & Business plans"
|
||||
:buttons="[unlimitedAddOnButton]"
|
||||
>
|
||||
<template #subtitle>
|
||||
<p class="text-body pt-1">
|
||||
<span class="font-medium">$0</span>
|
||||
per editor/month
|
||||
</p>
|
||||
<div class="flex items-center gap-x-2 mt-3 px-1">
|
||||
<FormSwitch
|
||||
v-model="isYearlyIntervalSelected"
|
||||
:show-label="false"
|
||||
name="billing-interval"
|
||||
/>
|
||||
<span class="text-body-2xs">Billed yearly</span>
|
||||
<CommonBadge rounded color-classes="text-foreground-2 bg-primary-muted">
|
||||
-10%
|
||||
</CommonBadge>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsWorkspacesBillingAddOnsCard>
|
||||
|
||||
<SettingsWorkspacesBillingAddOnsCard
|
||||
title="Extra data regions"
|
||||
subtitle="Talk to us"
|
||||
info="Access to almost all data residency regions."
|
||||
disclaimer="Only on Business plan"
|
||||
:buttons="[contactButton]"
|
||||
/>
|
||||
|
||||
<SettingsWorkspacesBillingAddOnsCard
|
||||
title="Priority support"
|
||||
subtitle="Talk to us"
|
||||
info="Private support channel for your workspace."
|
||||
disclaimer="Only on Business plan"
|
||||
:buttons="[contactButton]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BillingInterval } from '~/lib/common/generated/gql/graphql'
|
||||
import { useWorkspacePlan } from '~~/lib/workspaces/composables/plan'
|
||||
import { isPaidPlan } from '@speckle/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
slug: string
|
||||
}>()
|
||||
|
||||
const isYearlyIntervalSelected = ref(false)
|
||||
const { billingInterval, isBusinessPlan, plan } = useWorkspacePlan(props.slug)
|
||||
|
||||
const contactButton = computed(() => ({
|
||||
text: 'Contact us',
|
||||
id: 'contact-us',
|
||||
disabled: !isBusinessPlan.value,
|
||||
disabledMessage: 'Only available on the Business plan',
|
||||
onClick: () => {
|
||||
// TODO: Implement contact us
|
||||
}
|
||||
}))
|
||||
|
||||
const unlimitedAddOnButton = computed(() => ({
|
||||
text: 'Buy add-on',
|
||||
id: 'buy-add-on',
|
||||
disabled: plan.value?.name ? !isPaidPlan(plan.value.name) : false,
|
||||
disabledMessage: 'Only available on Starter and Business plans',
|
||||
onClick: () => {
|
||||
// TODO: Implement checkout
|
||||
}
|
||||
}))
|
||||
|
||||
watch(
|
||||
() => billingInterval.value,
|
||||
(newVal) => {
|
||||
isYearlyIntervalSelected.value = newVal === BillingInterval.Yearly
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<CommonCard class="flex flex-col gap-y-4 p-6">
|
||||
<div class="flex flex-col">
|
||||
<h3 class="text-body font-medium">{{ title }}</h3>
|
||||
<p v-if="subtitle" class="text-foreground-3 text-body-sm pt-1">{{ subtitle }}</p>
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
<p class="flex-1 mb-3">
|
||||
<span v-if="info" class="text-foreground-2 text-body-xs">{{ info }}</span>
|
||||
<slot name="info" />
|
||||
</p>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div
|
||||
v-for="(button, index) in buttons"
|
||||
:key="button.id || index"
|
||||
v-tippy="button.disabledMessage"
|
||||
>
|
||||
<FormButton
|
||||
v-bind="button.props || {}"
|
||||
:disabled="button.props?.disabled || button.disabled"
|
||||
size="sm"
|
||||
color="outline"
|
||||
@click="($event) => button.onClick?.($event)"
|
||||
>
|
||||
{{ button.text }}
|
||||
</FormButton>
|
||||
</div>
|
||||
<p v-if="disclaimer" class="font-medium text-foreground-2 text-body-2xs">
|
||||
{{ disclaimer }}
|
||||
</p>
|
||||
</div>
|
||||
</CommonCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
subtitle?: string
|
||||
info?: string
|
||||
buttons?: LayoutDialogButton[]
|
||||
disclaimer?: string
|
||||
}>()
|
||||
</script>
|
||||
@@ -62,6 +62,11 @@ export const useWorkspacePlan = (slug: string) => {
|
||||
const plan = computed(() => result.value?.workspaceBySlug?.plan)
|
||||
|
||||
const isFreePlan = computed(() => plan.value?.name === UnpaidWorkspacePlans.Free)
|
||||
const isBusinessPlan = computed(
|
||||
() =>
|
||||
plan.value?.name === PaidWorkspacePlansNew.Pro ||
|
||||
plan.value?.name === PaidWorkspacePlansNew.ProUnlimited
|
||||
)
|
||||
const isUnlimitedPlan = computed(
|
||||
() => plan.value?.name === UnpaidWorkspacePlans.Unlimited
|
||||
)
|
||||
@@ -129,6 +134,7 @@ export const useWorkspacePlan = (slug: string) => {
|
||||
seats,
|
||||
hasAvailableEditorSeats,
|
||||
editorSeatPriceFormatted,
|
||||
isUnlimitedPlan
|
||||
isUnlimitedPlan,
|
||||
isBusinessPlan
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,15 @@ const buildWorkspaceSUT = (
|
||||
getWorkspaceSsoProvider: async () => ({
|
||||
providerId: 'provider-id'
|
||||
}),
|
||||
getWorkspaceSsoSession: async () => ({
|
||||
userId: 'user-id',
|
||||
providerId: 'provider-id',
|
||||
validUntil: new Date()
|
||||
}),
|
||||
getWorkspaceSsoSession: async () => {
|
||||
const validUntil = new Date()
|
||||
validUntil.setDate(validUntil.getDate() + 7)
|
||||
return {
|
||||
userId: 'user-id',
|
||||
providerId: 'provider-id',
|
||||
validUntil
|
||||
}
|
||||
},
|
||||
...overrides
|
||||
})
|
||||
|
||||
|
||||
@@ -146,6 +146,31 @@ export const isSelfServeAvailablePlan = (plan: WorkspacePlans): boolean => {
|
||||
}
|
||||
}
|
||||
|
||||
export const isPaidPlan = (plan: WorkspacePlans): boolean => {
|
||||
switch (plan) {
|
||||
case 'team':
|
||||
case 'teamUnlimited':
|
||||
case 'pro':
|
||||
case 'proUnlimited':
|
||||
return true
|
||||
case 'free':
|
||||
case 'starter':
|
||||
case 'plus':
|
||||
case 'business':
|
||||
case 'starterInvoiced':
|
||||
case 'plusInvoiced':
|
||||
case 'businessInvoiced':
|
||||
case 'teamUnlimitedInvoiced':
|
||||
case 'proUnlimitedInvoiced':
|
||||
case 'unlimited':
|
||||
case 'academia':
|
||||
return false
|
||||
|
||||
default:
|
||||
throwUncoveredError(plan)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BILLING INTERVALS
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user