Feat: Add cancel billing sessions, billing alerts, assume no plan is trial plan (#3467)

This commit is contained in:
Mike 2024-11-16 20:33:03 +01:00 коммит произвёл GitHub
Родитель adeb529826
Коммит 423350eb64
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
11 изменённых файлов: 312 добавлений и 128 удалений

Просмотреть файл

@ -0,0 +1,102 @@
<template>
<CommonCard class="bg-foundation py-3 px-4">
<div class="flex gap-x-2">
<ExclamationCircleIcon v-if="showIcon" class="h-4 w-4 text-danger mt-1" />
<div class="flex-1 flex gap-x-4 items-center">
<div class="flex-1">
<h5 class="text-body-xs font-medium text-foreground">{{ title }}</h5>
<p class="text-body-xs text-foreground-2">{{ description }}</p>
</div>
<FormButton
v-if="isPaymentFailed"
:icon-right="ArrowTopRightOnSquareIcon"
@click="billingPortalRedirect(workspace.id)"
>
Update payment information
</FormButton>
</div>
</div>
</CommonCard>
</template>
<script setup lang="ts">
import {
ExclamationCircleIcon,
ArrowTopRightOnSquareIcon
} from '@heroicons/vue/24/outline'
import { graphql } from '~/lib/common/generated/gql'
import {
type BillingAlert_WorkspaceFragment,
WorkspacePlanStatuses,
WorkspacePlans
} from '~/lib/common/generated/gql/graphql'
import { useBillingActions } from '~/lib/billing/composables/actions'
graphql(`
fragment BillingAlert_Workspace on Workspace {
id
plan {
name
status
}
subscription {
billingInterval
currentBillingCycleEnd
}
}
`)
const props = defineProps<{
workspace: BillingAlert_WorkspaceFragment
}>()
const { billingPortalRedirect } = useBillingActions()
const planStatus = computed(() => props.workspace.plan?.status)
// If there is no plan status, we assume it's a trial
const isTrial = computed(
() => !planStatus.value || planStatus.value === WorkspacePlanStatuses.Trial
)
const isPaymentFailed = computed(
() => planStatus.value === WorkspacePlanStatuses.PaymentFailed
)
const title = computed(() => {
if (isTrial.value) {
return `You are currently on a free ${
props.workspace.plan?.name ?? WorkspacePlans.Team
} plan trial`
}
switch (planStatus.value) {
case WorkspacePlanStatuses.CancelationScheduled:
return `Your ${props.workspace.plan?.name} plan subscription is scheduled for cancelation`
case WorkspacePlanStatuses.Canceled:
return `Your ${props.workspace.plan?.name} plan subscription has been canceled`
case WorkspacePlanStatuses.Expired:
return `Your free ${props.workspace.plan?.name} plan trial has ended`
case WorkspacePlanStatuses.PaymentFailed:
return "Your last payment didn't go through"
default:
return ''
}
})
const description = computed(() => {
if (isTrial.value) {
return 'Upgrade to a paid plan to start your subscription.'
}
switch (planStatus.value) {
case WorkspacePlanStatuses.CancelationScheduled:
return 'Your workspace subscription is scheduled for cancelation. After the cancelation, your workspace will be in read-only mode.'
case WorkspacePlanStatuses.Canceled:
return 'Your workspace has been canceled and is in read-only mode. Upgrade your plan to continue.'
case WorkspacePlanStatuses.Expired:
return "The workspace is in a read-only locked state until there's an active subscription. Upgrade your plan to continue."
case WorkspacePlanStatuses.PaymentFailed:
return "Update your payment information now to ensure your workspace doesn't go into maintenance mode."
default:
return ''
}
})
const showIcon = computed(() => {
return !!planStatus.value && planStatus.value !== WorkspacePlanStatuses.Trial
})
</script>

Просмотреть файл

@ -1,59 +0,0 @@
<template>
<ul>
<li
v-for="(item, index) in billingItems"
:key="item.name"
class="text-body-xs flex"
:class="[index === billingItems.length - 1 ? 'border-b border-outline-3' : null]"
>
<p
class="text-foreground flex-1 py-2 px-3"
:class="[index < billingItems.length - 1 ? 'border-b border-outline-3' : null]"
>
{{ item.label }}
<span class="text-foreground-2">x</span>
£{{ item.cost }}
</p>
<p
class="text-right text-foreground ml-4 w-32 md:w-40 py-2 px-3"
:class="[index < billingItems.length - 1 ? 'border-b border-outline-3' : null]"
>
£{{ item.count * item.cost }} / month
</p>
</li>
<li class="flex justify-between text-foreground font-medium">
<p class="flex-1 p-3">Total</p>
<p class="text-right w-32 md:w-40 ml-4 p-3">
£{{ workspaceCost.subTotal }} / month
</p>
</li>
</ul>
</template>
<script lang="ts" setup>
import { graphql } from '~/lib/common/generated/gql'
import type { BillingSummary_WorkspaceCostFragment } from '~~/lib/common/generated/gql/graphql'
graphql(`
fragment BillingSummary_WorkspaceCost on WorkspaceCost {
items {
cost
count
name
label
}
discount {
amount
name
}
subTotal
total
}
`)
const props = defineProps<{
workspaceCost: BillingSummary_WorkspaceCostFragment
}>()
const billingItems = computed(() => props.workspaceCost.items)
</script>

Просмотреть файл

@ -3,22 +3,28 @@
<div class="md:max-w-4xl md:mx-auto pb-6 md:pb-0">
<SettingsSectionHeader title="Billing" text="Your workspace billing details" />
<template v-if="isBillingIntegrationEnabled">
<BillingAlert
v-if="workspaceResult && !isValidPlan"
:workspace="workspaceResult.workspace"
class="mb-4"
/>
<div class="flex flex-col gap-y-4 md:gap-y-6">
<SettingsSectionHeader title="Billing summary" subheading class="pt-4" />
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
<CommonCard class="gap-y-1 bg-foundation">
<p class="text-body-xs text-foreground-2 font-medium">
<p class="text-body-xs text-foreground-2">
{{ isTrialPeriod ? 'Trial plan' : 'Current plan' }}
</p>
<h4 class="text-heading-lg text-foreground capitalize">
{{ currentPlan?.name }} plan
{{ currentPlan?.name ?? WorkspacePlans.Team }} plan
</h4>
<p
v-if="currentPlan?.name && subscription?.billingInterval"
class="text-body-xs text-foreground-2"
>
<p class="text-body-xs text-foreground-2">
£{{ seatPrice }} per seat/month, billed
{{ subscription?.billingInterval }}
{{
subscription?.billingInterval === BillingInterval.Yearly
? 'yearly'
: 'monthly'
}}
</p>
</CommonCard>
<CommonCard class="gap-y-1 bg-foundation">
@ -26,9 +32,9 @@
{{
isTrialPeriod
? 'Expected bill'
: subscription?.billingInterval === BillingInterval.Monthly
? 'Monthly bill'
: 'Yearly bill'
: subscription?.billingInterval === BillingInterval.Yearly
? 'Yearly bill'
: 'Monthly bill'
}}
</p>
<h4 class="text-heading-lg text-foreground capitalize">Coming soon</h4>
@ -38,14 +44,16 @@
{{ isTrialPeriod ? 'First payment due' : 'Next payment due' }}
</p>
<h4 class="text-heading-lg text-foreground capitalize">
{{
isPaidPlan
? dayjs(subscription?.currentBillingCycleEnd).format('MMMM D, YYYY')
: 'Never'
}}
{{ nextPaymentDue }}
</h4>
<p v-if="isPaidPlan" class="text-body-xs text-foreground-2">
<span class="capitalize">{{ subscription?.billingInterval }}</span>
<span class="capitalize">
{{
subscription?.billingInterval === BillingInterval.Yearly
? 'Yearly'
: 'Monthly'
}}
</span>
billing period
</p>
</CommonCard>
@ -60,7 +68,7 @@
<FormButton
color="outline"
:icon-right="ArrowTopRightOnSquareIcon"
@click="openCustomerPortal"
@click="billingPortalRedirect(workspaceId)"
>
Open billing portal
</FormButton>
@ -110,11 +118,10 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { graphql } from '~/lib/common/generated/gql'
import { useQuery, useApolloClient } from '@vue/apollo-composable'
import { useQuery } from '@vue/apollo-composable'
import {
settingsWorkspaceBillingQuery,
settingsWorkspacePricingPlansQuery,
settingsWorkspaceBillingCustomerPortalQuery
settingsWorkspacePricingPlansQuery
} from '~/lib/settings/graphql/queries'
import { useIsBillingIntegrationEnabled } from '~/composables/globals'
import {
@ -124,9 +131,13 @@ import {
} from '~/lib/common/generated/gql/graphql'
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
import { isWorkspacePricingPlans } from '~/lib/settings/helpers/types'
import { useBillingActions } from '~/lib/billing/composables/actions'
import type { SeatPrices } from '~/lib/billing/helpers/types'
import { seatPricesConfig } from '~/lib/billing/helpers/constants'
graphql(`
fragment SettingsWorkspacesBilling_Workspace on Workspace {
...BillingAlert_Workspace
id
plan {
name
@ -139,33 +150,33 @@ graphql(`
}
`)
type SeatPrices = {
[key in WorkspacePlans]: {
[BillingInterval.Monthly]: number
[BillingInterval.Yearly]: number
}
}
const props = defineProps<{
workspaceId: string
}>()
const isBillingIntegrationEnabled = useIsBillingIntegrationEnabled()
const isYearlyPlan = ref(false)
// TODO: get these from the backend when available
const seatPrices = ref<SeatPrices>({
[WorkspacePlans.Team]: { monthly: 12, yearly: 10 },
[WorkspacePlans.Pro]: { monthly: 40, yearly: 36 },
[WorkspacePlans.Business]: { monthly: 79, yearly: 63 },
[WorkspacePlans.Academia]: { monthly: 0, yearly: 0 },
[WorkspacePlans.Unlimited]: { monthly: 0, yearly: 0 }
})
const seatPrices = ref<SeatPrices>(seatPricesConfig)
const { client: apollo } = useApolloClient()
const { result: workspaceResult } = useQuery(settingsWorkspaceBillingQuery, () => ({
workspaceId: props.workspaceId
}))
const { result: pricingPlansResult } = useQuery(settingsWorkspacePricingPlansQuery)
const route = useRoute()
const { result: workspaceResult } = useQuery(
settingsWorkspaceBillingQuery,
() => ({
workspaceId: props.workspaceId
}),
() => ({
enabled: isBillingIntegrationEnabled
})
)
const { result: pricingPlansResult } = useQuery(
settingsWorkspacePricingPlansQuery,
null,
() => ({
enabled: isBillingIntegrationEnabled
})
)
const { billingPortalRedirect, upgradePlanRedirect, cancelCheckoutSession } =
useBillingActions()
const currentPlan = computed(() => workspaceResult.value?.workspace.plan)
const subscription = computed(() => workspaceResult.value?.workspace.subscription)
@ -175,39 +186,50 @@ const isPaidPlan = computed(
currentPlan.value?.name !== WorkspacePlans.Unlimited
)
const isTrialPeriod = computed(
() => currentPlan.value?.status === WorkspacePlanStatuses.Trial
() =>
currentPlan.value?.status === WorkspacePlanStatuses.Trial ||
!currentPlan.value?.status
)
const isActivePlan = computed(
() =>
currentPlan.value &&
currentPlan.value?.status !== WorkspacePlanStatuses.Trial &&
currentPlan.value?.status !== WorkspacePlanStatuses.Canceled
)
const isValidPlan = computed(
() => currentPlan.value?.status === WorkspacePlanStatuses.Valid
)
const seatPrice = computed(() =>
currentPlan.value && subscription.value
? seatPrices.value[currentPlan.value?.name][subscription.value?.billingInterval]
: 0
: seatPrices.value[WorkspacePlans.Team][BillingInterval.Monthly]
)
const pricingPlans = computed(() =>
isWorkspacePricingPlans(pricingPlansResult.value)
? pricingPlansResult.value?.workspacePricingPlans.workspacePlanInformation
: undefined
)
const nextPaymentDue = computed(() =>
currentPlan.value
? isPaidPlan.value
? dayjs(subscription.value?.currentBillingCycleEnd).format('MMMM D, YYYY')
: 'Never'
: dayjs().add(30, 'days').format('MMMM D, YYYY')
)
const onUpgradePlanClick = (plan: WorkspacePlans) => {
const cycle = isYearlyPlan.value ? BillingInterval.Yearly : BillingInterval.Monthly
window.location.href = `/api/v1/billing/workspaces/${props.workspaceId}/checkout-session/${plan}/${cycle}`
}
const openCustomerPortal = async () => {
// We need to fetch this on click because the link expires very quickly
const result = await apollo.query({
query: settingsWorkspaceBillingCustomerPortalQuery,
variables: { workspaceId: props.workspaceId },
fetchPolicy: 'no-cache'
upgradePlanRedirect({
plan,
cycle: isYearlyPlan.value ? BillingInterval.Yearly : BillingInterval.Monthly,
workspaceId: props.workspaceId
})
if (result.data?.workspace.customerPortalUrl) {
window.location.href = result.data.workspace.customerPortalUrl
}
}
onMounted(() => {
const paymentStatusQuery = route.query?.payment_status
const sessionIdQuery = route.query?.session_id
if (sessionIdQuery && String(paymentStatusQuery) === WorkspacePlanStatuses.Canceled) {
cancelCheckoutSession(String(sessionIdQuery), props.workspaceId)
}
})
</script>

Просмотреть файл

@ -11,6 +11,11 @@
:separator="false"
/>
</Portal>
<BillingAlert
v-if="initialQueryResult && !hasValidPlan"
:workspace="initialQueryResult.workspaceBySlug"
class="mb-4"
/>
<WorkspaceHeader
v-if="workspace"
:icon="Squares2X2Icon"
@ -127,6 +132,7 @@ import {
SettingMenuKeys,
type AvailableSettingsMenuKeys
} from '~/lib/settings/helpers/types'
import { WorkspacePlanStatuses } from '~/lib/common/generated/gql/graphql'
graphql(`
fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {
@ -205,6 +211,9 @@ const { query, identifier, onInfiniteLoad } = usePaginatedQuery({
const projects = computed(() => query.result.value?.workspaceBySlug?.projects)
const workspaceInvite = computed(() => initialQueryResult.value?.workspaceInvite)
const workspace = computed(() => initialQueryResult.value?.workspaceBySlug)
const hasValidPlan = computed(
() => workspace.value?.plan?.status === WorkspacePlanStatuses.Valid
)
const isWorkspaceGuest = computed(() => workspace.value?.role === Roles.Workspace.Guest)
const isWorkspaceAdmin = computed(() => workspace.value?.role === Roles.Workspace.Admin)
const showEmptyState = computed(() => {

Просмотреть файл

@ -0,0 +1,48 @@
import { useApolloClient, useMutation } from '@vue/apollo-composable'
import { settingsWorkspaceBillingCustomerPortalQuery } from '~/lib/settings/graphql/queries'
import type {
WorkspacePlans,
BillingInterval
} from '~/lib/common/generated/gql/graphql'
import { settingsBillingCancelCheckoutSessionMutation } from '~/lib/settings/graphql/mutations'
export const useBillingActions = () => {
const { client: apollo } = useApolloClient()
const { mutate: cancelCheckoutSessionMutation } = useMutation(
settingsBillingCancelCheckoutSessionMutation
)
const billingPortalRedirect = async (workspaceId: string) => {
// We need to fetch this on click because the link expires very quickly
const result = await apollo.query({
query: settingsWorkspaceBillingCustomerPortalQuery,
variables: { workspaceId },
fetchPolicy: 'no-cache'
})
if (result.data?.workspace.customerPortalUrl) {
window.location.href = result.data.workspace.customerPortalUrl
}
}
const upgradePlanRedirect = (args: {
plan: WorkspacePlans
cycle: BillingInterval
workspaceId: string
}) => {
const { plan, cycle, workspaceId } = args
window.location.href = `/api/v1/billing/workspaces/${workspaceId}/checkout-session/${plan}/${cycle}`
}
const cancelCheckoutSession = async (sessionId: string, workspaceId: string) => {
await cancelCheckoutSessionMutation({
input: { sessionId, workspaceId }
})
}
return {
billingPortalRedirect,
upgradePlanRedirect,
cancelCheckoutSession
}
}

Просмотреть файл

@ -0,0 +1,26 @@
import { WorkspacePlans, BillingInterval } from '~/lib/common/generated/gql/graphql'
import type { SeatPrices } from '~/lib/billing/helpers/types'
// TODO: get these from the backend when available
export const seatPricesConfig: SeatPrices = {
[WorkspacePlans.Team]: {
[BillingInterval.Monthly]: 12,
[BillingInterval.Yearly]: 10
},
[WorkspacePlans.Pro]: {
[BillingInterval.Monthly]: 40,
[BillingInterval.Yearly]: 36
},
[WorkspacePlans.Business]: {
[BillingInterval.Monthly]: 79,
[BillingInterval.Yearly]: 63
},
[WorkspacePlans.Academia]: {
[BillingInterval.Monthly]: 0,
[BillingInterval.Yearly]: 0
},
[WorkspacePlans.Unlimited]: {
[BillingInterval.Monthly]: 0,
[BillingInterval.Yearly]: 0
}
}

Просмотреть файл

@ -0,0 +1,11 @@
import type {
WorkspacePlans,
BillingInterval
} from '~/lib/common/generated/gql/graphql'
export type SeatPrices = {
[key in WorkspacePlans]: {
[BillingInterval.Monthly]: number
[BillingInterval.Yearly]: number
}
}

Просмотреть файл

@ -40,7 +40,7 @@ const documents = {
"\n fragment AutomateRunsTriggerStatusDialogRunsRows_AutomateRun on AutomateRun {\n id\n functionRuns {\n id\n ...AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun\n }\n ...AutomationsStatusOrderedRuns_AutomationRun\n }\n": types.AutomateRunsTriggerStatusDialogRunsRows_AutomateRunFragmentDoc,
"\n fragment AutomateViewerPanel_AutomateRun on AutomateRun {\n id\n functionRuns {\n id\n ...AutomateViewerPanelFunctionRunRow_AutomateFunctionRun\n }\n ...AutomationsStatusOrderedRuns_AutomationRun\n }\n": types.AutomateViewerPanel_AutomateRunFragmentDoc,
"\n fragment AutomateViewerPanelFunctionRunRow_AutomateFunctionRun on AutomateFunctionRun {\n id\n results\n status\n statusMessage\n contextView\n function {\n id\n logo\n name\n }\n createdAt\n updatedAt\n }\n": types.AutomateViewerPanelFunctionRunRow_AutomateFunctionRunFragmentDoc,
"\n fragment BillingSummary_WorkspaceCost on WorkspaceCost {\n items {\n cost\n count\n name\n label\n }\n discount {\n amount\n name\n }\n subTotal\n total\n }\n": types.BillingSummary_WorkspaceCostFragmentDoc,
"\n fragment BillingAlert_Workspace on Workspace {\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n": types.BillingAlert_WorkspaceFragmentDoc,
"\n fragment CommonModelSelectorModel on Model {\n id\n name\n }\n": types.CommonModelSelectorModelFragmentDoc,
"\n fragment DashboardProjectCard_Project on Project {\n id\n name\n role\n updatedAt\n models {\n totalCount\n }\n team {\n user {\n ...LimitedUserAvatar\n }\n }\n workspace {\n id\n slug\n name\n ...WorkspaceAvatar_Workspace\n }\n }\n": types.DashboardProjectCard_ProjectFragmentDoc,
"\n fragment FormSelectModels_Model on Model {\n id\n name\n }\n": types.FormSelectModels_ModelFragmentDoc,
@ -114,7 +114,7 @@ const documents = {
"\n fragment SettingsUserProfileDeleteAccount_User on User {\n id\n email\n }\n": types.SettingsUserProfileDeleteAccount_UserFragmentDoc,
"\n fragment SettingsUserProfileDetails_User on User {\n id\n name\n company\n ...UserProfileEditDialogAvatar_User\n }\n": types.SettingsUserProfileDetails_UserFragmentDoc,
"\n fragment UserProfileEditDialogAvatar_User on User {\n id\n avatar\n ...ActiveUserAvatar\n }\n": types.UserProfileEditDialogAvatar_UserFragmentDoc,
"\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n": types.SettingsWorkspacesBilling_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n ...BillingAlert_Workspace\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n": types.SettingsWorkspacesBilling_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneral_Workspace on Workspace {\n ...SettingsWorkspacesGeneralEditAvatar_Workspace\n ...SettingsWorkspaceGeneralDeleteDialog_Workspace\n ...SettingsWorkspacesGeneralEditSlugDialog_Workspace\n id\n name\n slug\n description\n logo\n role\n defaultProjectRole\n }\n": types.SettingsWorkspacesGeneral_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspaceGeneralDeleteDialog_Workspace on Workspace {\n id\n name\n }\n": types.SettingsWorkspaceGeneralDeleteDialog_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneralEditAvatar_Workspace on Workspace {\n id\n logo\n name\n defaultLogoIndex\n }\n": types.SettingsWorkspacesGeneralEditAvatar_WorkspaceFragmentDoc,
@ -288,6 +288,7 @@ const documents = {
"\n mutation AddWorkspaceDomain($input: AddDomainToWorkspaceInput!) {\n workspaceMutations {\n addDomain(input: $input) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n }\n }\n": types.AddWorkspaceDomainDocument,
"\n mutation DeleteWorkspaceDomain($input: WorkspaceDomainDeleteInput!) {\n workspaceMutations {\n deleteDomain(input: $input) {\n ...SettingsWorkspacesSecurityDomainRemoveDialog_Workspace\n }\n }\n }\n": types.DeleteWorkspaceDomainDocument,
"\n mutation SettingsLeaveWorkspace($leaveId: ID!) {\n workspaceMutations {\n leave(id: $leaveId)\n }\n }\n": types.SettingsLeaveWorkspaceDocument,
"\n mutation SettingsBillingCancelCheckoutSession($input: CancelCheckoutSessionInput!) {\n workspaceMutations {\n billing {\n cancelCheckoutSession(input: $input)\n }\n }\n }\n": types.SettingsBillingCancelCheckoutSessionDocument,
"\n query SettingsSidebar {\n activeUser {\n ...SettingsDialog_User\n }\n }\n": types.SettingsSidebarDocument,
"\n query SettingsWorkspaceGeneral($id: String!) {\n workspace(id: $id) {\n ...SettingsWorkspacesGeneral_Workspace\n }\n }\n": types.SettingsWorkspaceGeneralDocument,
"\n query SettingsWorkspaceBilling($workspaceId: String!) {\n workspace(id: $workspaceId) {\n id\n ...SettingsWorkspacesBilling_Workspace\n }\n }\n": types.SettingsWorkspaceBillingDocument,
@ -333,7 +334,7 @@ const documents = {
"\n mutation ProcessWorkspaceInvite($input: WorkspaceInviteUseInput!) {\n workspaceMutations {\n invites {\n use(input: $input)\n }\n }\n }\n": types.ProcessWorkspaceInviteDocument,
"\n mutation SetDefaultWorkspaceRegion($workspaceId: String!, $regionKey: String!) {\n workspaceMutations {\n setDefaultRegion(workspaceId: $workspaceId, regionKey: $regionKey) {\n id\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n }\n": types.SetDefaultWorkspaceRegionDocument,
"\n query WorkspaceAccessCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n }\n }\n": types.WorkspaceAccessCheckDocument,
"\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n ...MoveProjectsDialog_Workspace\n ...WorkspaceHeader_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n projectListProject: projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspacePageQueryDocument,
"\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n ...MoveProjectsDialog_Workspace\n ...WorkspaceHeader_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n ...BillingAlert_Workspace\n projectListProject: projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspacePageQueryDocument,
"\n query WorkspaceProjectsQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n }\n": types.WorkspaceProjectsQueryDocument,
"\n query WorkspaceInvite(\n $workspaceId: String\n $token: String\n $options: WorkspaceInviteLookupOptions\n ) {\n workspaceInvite(workspaceId: $workspaceId, token: $token, options: $options) {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspaceInviteDocument,
"\n query MoveProjectsDialog {\n activeUser {\n ...MoveProjectsDialog_User\n }\n }\n": types.MoveProjectsDialogDocument,
@ -473,7 +474,7 @@ export function graphql(source: "\n fragment AutomateViewerPanelFunctionRunRow_
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment BillingSummary_WorkspaceCost on WorkspaceCost {\n items {\n cost\n count\n name\n label\n }\n discount {\n amount\n name\n }\n subTotal\n total\n }\n"): (typeof documents)["\n fragment BillingSummary_WorkspaceCost on WorkspaceCost {\n items {\n cost\n count\n name\n label\n }\n discount {\n amount\n name\n }\n subTotal\n total\n }\n"];
export function graphql(source: "\n fragment BillingAlert_Workspace on Workspace {\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n"): (typeof documents)["\n fragment BillingAlert_Workspace on Workspace {\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@ -769,7 +770,7 @@ export function graphql(source: "\n fragment UserProfileEditDialogAvatar_User o
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n ...BillingAlert_Workspace\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n ...BillingAlert_Workspace\n id\n plan {\n name\n status\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@ -1462,6 +1463,10 @@ export function graphql(source: "\n mutation DeleteWorkspaceDomain($input: Work
* 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 SettingsLeaveWorkspace($leaveId: ID!) {\n workspaceMutations {\n leave(id: $leaveId)\n }\n }\n"): (typeof documents)["\n mutation SettingsLeaveWorkspace($leaveId: ID!) {\n workspaceMutations {\n leave(id: $leaveId)\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 SettingsBillingCancelCheckoutSession($input: CancelCheckoutSessionInput!) {\n workspaceMutations {\n billing {\n cancelCheckoutSession(input: $input)\n }\n }\n }\n"): (typeof documents)["\n mutation SettingsBillingCancelCheckoutSession($input: CancelCheckoutSessionInput!) {\n workspaceMutations {\n billing {\n cancelCheckoutSession(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.
*/
@ -1645,7 +1650,7 @@ export function graphql(source: "\n query WorkspaceAccessCheck($slug: String!)
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n ...MoveProjectsDialog_Workspace\n ...WorkspaceHeader_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n projectListProject: projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n"): (typeof documents)["\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n ...MoveProjectsDialog_Workspace\n ...WorkspaceHeader_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n projectListProject: projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n"];
export function graphql(source: "\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n ...MoveProjectsDialog_Workspace\n ...WorkspaceHeader_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n ...BillingAlert_Workspace\n projectListProject: projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n"): (typeof documents)["\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n ...MoveProjectsDialog_Workspace\n ...WorkspaceHeader_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n ...BillingAlert_Workspace\n projectListProject: projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -117,6 +117,7 @@ export const settingsDeleteWorkspaceDomainMutation = graphql(`
}
}
`)
export const settingsLeaveWorkspaceMutation = graphql(`
mutation SettingsLeaveWorkspace($leaveId: ID!) {
workspaceMutations {
@ -124,3 +125,13 @@ export const settingsLeaveWorkspaceMutation = graphql(`
}
}
`)
export const settingsBillingCancelCheckoutSessionMutation = graphql(`
mutation SettingsBillingCancelCheckoutSession($input: CancelCheckoutSessionInput!) {
workspaceMutations {
billing {
cancelCheckoutSession(input: $input)
}
}
}
`)

Просмотреть файл

@ -21,6 +21,7 @@ export const workspacePageQuery = graphql(`
...MoveProjectsDialog_Workspace
...WorkspaceHeader_Workspace
...WorkspaceMixpanelUpdateGroup_Workspace
...BillingAlert_Workspace
projectListProject: projects(filter: $filter, cursor: $cursor, limit: 10) {
...WorkspaceProjectList_ProjectCollection
}