Skip to content

Commit

Permalink
gergo/web 2635 set up new plans in the backend (#4031)
Browse files Browse the repository at this point in the history
* feat(gatekeeper): add new free plan option as an unpaid workspace plan

* feat(workspaces): add feature flag for new plans

* feat(gatekeeper): add free plan option to workspace plans

* Added button in FE

* Make button more beautifuller

* feat(gatekeeper): enable creating workspaces on a free plan

* Fix

* Fix FE linting

---------

Co-authored-by: Mike Tasset <[email protected]>
  • Loading branch information
gjedlicska and Mikehrn authored Feb 21, 2025
1 parent 90ef5bb commit 5b0bb3b
Show file tree
Hide file tree
Showing 15 changed files with 132 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ const canUpgradeToPlan = computed(() => {
[WorkspacePlans.Unlimited]: [],
[WorkspacePlans.StarterInvoiced]: [],
[WorkspacePlans.PlusInvoiced]: [],
[WorkspacePlans.BusinessInvoiced]: []
[WorkspacePlans.BusinessInvoiced]: [],
[WorkspacePlans.Free]: []
}
return allowedUpgrades[props.currentPlan.name].includes(props.plan.name)
Expand Down
7 changes: 7 additions & 0 deletions packages/frontend-2/composables/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ export const useIsGendoModuleEnabled = () => {
return ref(FF_GENDOAI_MODULE_ENABLED)
}

export const useWorkspaceNewPlansEnabled = () => {
const {
public: { FF_WORKSPACES_NEW_PLANS_ENABLED }
} = useRuntimeConfig()
return ref(FF_WORKSPACES_NEW_PLANS_ENABLED)
}

export const useIsBillingIntegrationEnabled = () => {
const {
public: { FF_BILLING_INTEGRATION_ENABLED }
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend-2/lib/billing/graphql/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ export const billingUpgradePlanMuation = graphql(`
}
}
`)

export const adminUpdateWorkspacePlanMutation = graphql(`
mutation AdminUpdateWorkspacePlan($input: AdminUpdateWorkspacePlanInput!) {
admin {
updateWorkspacePlan(input: $input)
}
}
`)
5 changes: 5 additions & 0 deletions packages/frontend-2/lib/common/generated/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ const documents = {
"\n fragment BillingActions_Workspace on Workspace {\n id\n name\n invitedTeam(filter: $invitesFilter) {\n id\n }\n plan {\n name\n status\n }\n subscription {\n billingInterval\n }\n team {\n totalCount\n }\n defaultRegion {\n name\n }\n }\n": types.BillingActions_WorkspaceFragmentDoc,
"\n mutation BillingCreateCheckoutSession($input: CheckoutSessionInput!) {\n workspaceMutations {\n billing {\n createCheckoutSession(input: $input) {\n url\n id\n }\n }\n }\n }\n": types.BillingCreateCheckoutSessionDocument,
"\n mutation BillingUpgradePlan($input: UpgradePlanInput!) {\n workspaceMutations {\n billing {\n upgradePlan(input: $input)\n }\n }\n }\n": types.BillingUpgradePlanDocument,
"\n mutation AdminUpdateWorkspacePlan($input: AdminUpdateWorkspacePlanInput!) {\n admin {\n updateWorkspacePlan(input: $input)\n }\n }\n": types.AdminUpdateWorkspacePlanDocument,
"\n query MentionsUserSearch($query: String!, $projectId: String) {\n users(input: { query: $query, limit: 5, cursor: null, projectId: $projectId }) {\n items {\n id\n name\n company\n }\n }\n }\n": types.MentionsUserSearchDocument,
"\n query UserSearch(\n $query: String!\n $limit: Int\n $cursor: String\n $archived: Boolean\n $workspaceId: String\n ) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n role\n workspaceDomainPolicyCompliant(workspaceId: $workspaceId)\n }\n }\n }\n": types.UserSearchDocument,
"\n query ServerInfoBlobSizeLimit {\n serverInfo {\n configuration {\n blobSizeLimitBytes\n }\n }\n }\n": types.ServerInfoBlobSizeLimitDocument,
Expand Down Expand Up @@ -1048,6 +1049,10 @@ export function graphql(source: "\n mutation BillingCreateCheckoutSession($inpu
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation BillingUpgradePlan($input: UpgradePlanInput!) {\n workspaceMutations {\n billing {\n upgradePlan(input: $input)\n }\n }\n }\n"): (typeof documents)["\n mutation BillingUpgradePlan($input: UpgradePlanInput!) {\n workspaceMutations {\n billing {\n upgradePlan(input: $input)\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation AdminUpdateWorkspacePlan($input: AdminUpdateWorkspacePlanInput!) {\n admin {\n updateWorkspacePlan(input: $input)\n }\n }\n"): (typeof documents)["\n mutation AdminUpdateWorkspacePlan($input: AdminUpdateWorkspacePlanInput!) {\n admin {\n updateWorkspacePlan(input: $input)\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
9 changes: 9 additions & 0 deletions packages/frontend-2/lib/common/generated/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4557,6 +4557,7 @@ export enum WorkspacePlans {
Academia = 'academia',
Business = 'business',
BusinessInvoiced = 'businessInvoiced',
Free = 'free',
Plus = 'plus',
PlusInvoiced = 'plusInvoiced',
Starter = 'starter',
Expand Down Expand Up @@ -5155,6 +5156,13 @@ export type BillingUpgradePlanMutationVariables = Exact<{

export type BillingUpgradePlanMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', billing: { __typename?: 'WorkspaceBillingMutations', upgradePlan: boolean } } };

export type AdminUpdateWorkspacePlanMutationVariables = Exact<{
input: AdminUpdateWorkspacePlanInput;
}>;


export type AdminUpdateWorkspacePlanMutation = { __typename?: 'Mutation', admin: { __typename?: 'AdminMutations', updateWorkspacePlan: boolean } };

export type MentionsUserSearchQueryVariables = Exact<{
query: Scalars['String']['input'];
projectId?: InputMaybe<Scalars['String']['input']>;
Expand Down Expand Up @@ -6754,6 +6762,7 @@ export const AutomateFunctionsPagePaginationDocument = {"kind":"Document","defin
export const ActiveUserFunctionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserFunctions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automateFunctions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"2"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode<ActiveUserFunctionsQuery, ActiveUserFunctionsQueryVariables>;
export const BillingCreateCheckoutSessionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BillingCreateCheckoutSession"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CheckoutSessionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createCheckoutSession"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode<BillingCreateCheckoutSessionMutation, BillingCreateCheckoutSessionMutationVariables>;
export const BillingUpgradePlanDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BillingUpgradePlan"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpgradePlanInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upgradePlan"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]}}]} as unknown as DocumentNode<BillingUpgradePlanMutation, BillingUpgradePlanMutationVariables>;
export const AdminUpdateWorkspacePlanDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AdminUpdateWorkspacePlan"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AdminUpdateWorkspacePlanInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"admin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkspacePlan"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode<AdminUpdateWorkspacePlanMutation, AdminUpdateWorkspacePlanMutationVariables>;
export const MentionsUserSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MentionsUserSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"users"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"5"}},{"kind":"ObjectField","name":{"kind":"Name","value":"cursor"},"value":{"kind":"NullValue"}},{"kind":"ObjectField","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}}]}}]}}]}}]} as unknown as DocumentNode<MentionsUserSearchQuery, MentionsUserSearchQueryVariables>;
export const UserSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"archived"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userSearch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"archived"},"value":{"kind":"Variable","name":{"kind":"Name","value":"archived"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceDomainPolicyCompliant"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}]}]}}]}}]}}]} as unknown as DocumentNode<UserSearchQuery, UserSearchQueryVariables>;
export const ServerInfoBlobSizeLimitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfoBlobSizeLimit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configuration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blobSizeLimitBytes"}}]}}]}}]}}]} as unknown as DocumentNode<ServerInfoBlobSizeLimitQuery, ServerInfoBlobSizeLimitQueryVariables>;
Expand Down
26 changes: 25 additions & 1 deletion packages/frontend-2/pages/settings/workspaces/[slug]/billing.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<template>
<section>
<div class="md:max-w-5xl md:mx-auto pb-6 md:pb-0">
<FormButton
v-if="isWorkspaceNewPlansEnabled && isServerAdmin"
size="lg"
class="!bg-pink-500 !border-pink-700 mb-4"
@click="handleUpgradeClick"
>
𝓒𝓱𝓪𝓷𝓰𝓮 𝓽𝓸 𝓷𝓮𝔀 𝓹𝓵𝓪𝓷 💸
</FormButton>
<SettingsSectionHeader title="Billing" text="Your workspace billing details" />
<template v-if="isBillingIntegrationEnabled">
<div class="flex flex-col gap-y-4 md:gap-y-6">
Expand Down Expand Up @@ -198,7 +206,7 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { graphql } from '~/lib/common/generated/gql'
import { useQuery } from '@vue/apollo-composable'
import { useQuery, useMutation } from '@vue/apollo-composable'
import { settingsWorkspaceBillingQuery } from '~/lib/settings/graphql/queries'
import { useIsBillingIntegrationEnabled } from '~/composables/globals'
import {
Expand All @@ -215,6 +223,7 @@ import { InformationCircleIcon } from '@heroicons/vue/24/outline'
import { isPaidPlan } from '@/lib/billing/helpers/types'
import { useMixpanel } from '~/lib/core/composables/mp'
import { guideBillingUrl } from '~/lib/common/helpers/route'
import { adminUpdateWorkspacePlanMutation } from '~/lib/billing/graphql/mutations'
graphql(`
fragment SettingsWorkspacesBilling_Workspace on Workspace {
Expand Down Expand Up @@ -254,7 +263,9 @@ useHead({
const slug = computed(() => (route.params.slug as string) || '')
const { isAdmin: isServerAdmin } = useActiveUser()
const route = useRoute()
const isWorkspaceNewPlansEnabled = useWorkspaceNewPlansEnabled()
const isBillingIntegrationEnabled = useIsBillingIntegrationEnabled()
const { result: workspaceResult } = useQuery(
settingsWorkspaceBillingQuery,
Expand All @@ -267,6 +278,7 @@ const { result: workspaceResult } = useQuery(
)
const { billingPortalRedirect, redirectToCheckout } = useBillingActions()
const mixpanel = useMixpanel()
const { mutate: mutateWorkspacePlan } = useMutation(adminUpdateWorkspacePlanMutation)
const seatPrices = ref({
[WorkspacePlans.Starter]: pricingPlansConfig.plans[WorkspacePlans.Starter].cost,
Expand Down Expand Up @@ -437,4 +449,16 @@ const onPlanSelected = (plan: { name: WorkspacePlans; cycle: BillingInterval })
isUpgradeDialogOpen.value = true
}
}
const handleUpgradeClick = () => {
if (!workspace.value?.id) return
// Temporary hack to change workspace plans to the new free plan
mutateWorkspacePlan({
input: {
workspaceId: workspace.value?.id,
plan: WorkspacePlans.Free,
status: WorkspacePlanStatuses.Valid
}
})
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type WorkspaceBillingMutations {
}

enum WorkspacePlans {
free
starter
plus
business
Expand Down
21 changes: 16 additions & 5 deletions packages/server/modules/gatekeeper/domain/workspacePricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,24 @@ const baseFeatures = {
workspace: true
}

const free: WorkspacePlanFeaturesAndLimits = {
...baseFeatures,
name: 'free',
description: 'The free plan',
oidcSso: false,
workspaceDataRegionSpecificity: false,
automateMinutes: 300,
uploadSize: 100
}

const starter: WorkspacePlanFeaturesAndLimits = {
...baseFeatures,
name: 'starter',
description: 'The team plan',
oidcSso: false,
workspaceDataRegionSpecificity: false,
automateMinutes: 300,
uploadSize: 500
uploadSize: 100
}

const plus: WorkspacePlanFeaturesAndLimits = {
Expand All @@ -89,7 +99,7 @@ const plus: WorkspacePlanFeaturesAndLimits = {
oidcSso: true,
workspaceDataRegionSpecificity: false,
automateMinutes: 900,
uploadSize: 1000
uploadSize: 100
}

const business: WorkspacePlanFeaturesAndLimits = {
Expand All @@ -99,7 +109,7 @@ const business: WorkspacePlanFeaturesAndLimits = {
oidcSso: true,
workspaceDataRegionSpecificity: true,
automateMinutes: 900,
uploadSize: 1000
uploadSize: 100
}

const unlimited: WorkspacePlanFeaturesAndLimits = {
Expand All @@ -109,7 +119,7 @@ const unlimited: WorkspacePlanFeaturesAndLimits = {
oidcSso: true,
workspaceDataRegionSpecificity: true,
automateMinutes: null,
uploadSize: 1000
uploadSize: 100
}

const academia: WorkspacePlanFeaturesAndLimits = {
Expand All @@ -119,7 +129,7 @@ const academia: WorkspacePlanFeaturesAndLimits = {
oidcSso: true,
workspaceDataRegionSpecificity: true,
automateMinutes: 900,
uploadSize: 1000
uploadSize: 100
}

const paidWorkspacePlanFeatures: Record<
Expand All @@ -135,6 +145,7 @@ export const unpaidWorkspacePlanFeatures: Record<
UnpaidWorkspacePlans,
WorkspacePlanFeaturesAndLimits
> = {
free,
academia,
unlimited,
starterInvoiced: starter,
Expand Down
Loading

0 comments on commit 5b0bb3b

Please sign in to comment.