diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx
index a57f442f1d..9a1c329fd4 100644
--- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx
@@ -9,6 +9,7 @@ import type { ReactElement, SVGProps } from 'react';
import { AboutAffine } from './about';
import { AppearanceSettings } from './appearance';
+import { AFFiNECloudPlans } from './plans';
import { Plugins } from './plugins';
import { Shortcuts } from './shortcuts';
@@ -16,7 +17,9 @@ export type GeneralSettingKeys =
| 'shortcuts'
| 'appearance'
| 'plugins'
- | 'about';
+ | 'about'
+ | 'plans'
+ | 'billing';
interface GeneralSettingListItem {
key: GeneralSettingKeys;
@@ -43,6 +46,22 @@ export const useGeneralSettingList = (): GeneralSettingList => {
icon: KeyboardIcon,
testId: 'shortcuts-panel-trigger',
},
+ {
+ key: 'plans',
+ // TODO: i18n
+ title: 'AFFiNE Cloud Plans',
+ // TODO: icon
+ icon: KeyboardIcon,
+ testId: 'plans-panel-trigger',
+ },
+ {
+ key: 'billing',
+ // TODO: i18n
+ title: 'Billing',
+ // TODO: icon
+ icon: KeyboardIcon,
+ testId: 'billing-panel-trigger',
+ },
{
key: 'plugins',
title: 'Plugins',
@@ -72,6 +91,8 @@ export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => {
return ;
case 'about':
return ;
+ case 'plans':
+ return ;
default:
return null;
}
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx
new file mode 100644
index 0000000000..1c79de89f2
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx
@@ -0,0 +1,417 @@
+import { RadioButton, RadioButtonGroup } from '@affine/component';
+import { SettingHeader } from '@affine/component/setting-components';
+import {
+ cancelSubscriptionMutation,
+ checkoutMutation,
+ pricesQuery,
+ SubscriptionPlan,
+ subscriptionQuery,
+ SubscriptionRecurring,
+ updateSubscriptionMutation,
+} from '@affine/graphql';
+import { useMutation, useQuery } from '@affine/workspace/affine/gql';
+import { Button } from '@toeverything/components/button';
+import {
+ type PropsWithChildren,
+ Suspense,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+
+import * as styles from './style.css';
+
+interface FixedPrice {
+ type: 'fixed';
+ plan: SubscriptionPlan;
+ price: string;
+ yearlyPrice: string;
+ discount?: string;
+ benefits: string[];
+}
+
+interface DynamicPrice {
+ type: 'dynamic';
+ plan: SubscriptionPlan;
+ contact: boolean;
+ benefits: string[];
+}
+
+// TODO: i18n all things
+const planDetail = new Map([
+ [
+ SubscriptionPlan.Free,
+ {
+ type: 'fixed',
+ plan: SubscriptionPlan.Free,
+ price: '0',
+ yearlyPrice: '0',
+ benefits: [
+ 'Unlimited local workspace',
+ 'Unlimited login devices',
+ 'Unlimited blocks',
+ 'AFFiNE Cloud Storage 10GB',
+ 'The maximum file size is 10M',
+ 'Number of members per Workspace ≤ 3',
+ ],
+ },
+ ],
+ [
+ SubscriptionPlan.Pro,
+ {
+ type: 'fixed',
+ plan: SubscriptionPlan.Pro,
+ price: '1',
+ yearlyPrice: '1',
+ benefits: [
+ 'Unlimited local workspace',
+ 'Unlimited login devices',
+ 'Unlimited blocks',
+ 'AFFiNE Cloud Storage 100GB',
+ 'The maximum file size is 500M',
+ 'Number of members per Workspace ≤ 10',
+ ],
+ },
+ ],
+ [
+ SubscriptionPlan.Team,
+ {
+ type: 'dynamic',
+ plan: SubscriptionPlan.Team,
+ contact: true,
+ benefits: [
+ 'Best team workspace for collaboration and knowledge distilling.',
+ 'Focusing on what really matters with team project management and automation.',
+ 'Pay for seats, fits all team size.',
+ ],
+ },
+ ],
+ [
+ SubscriptionPlan.Enterprise,
+ {
+ type: 'dynamic',
+ plan: SubscriptionPlan.Enterprise,
+ contact: true,
+ benefits: [
+ 'Solutions & best practices for dedicated needs.',
+ 'Embedable & interrogations with IT support.',
+ ],
+ },
+ ],
+]);
+
+const Settings = () => {
+ const { data, mutate } = useQuery({
+ query: subscriptionQuery,
+ });
+
+ const {
+ data: { prices },
+ } = useQuery({
+ query: pricesQuery,
+ });
+
+ prices.forEach(price => {
+ const detail = planDetail.get(price.plan);
+
+ if (detail?.type === 'fixed') {
+ detail.price = (price.amount / 100).toFixed(2);
+ detail.yearlyPrice = (price.yearlyAmount / 100 / 12).toFixed(2);
+ detail.discount = (
+ (1 - price.yearlyAmount / 12 / price.amount) *
+ 100
+ ).toFixed(2);
+ }
+ });
+
+ const loggedIn = !!data.currentUser;
+ const subscription = data.currentUser?.subscription;
+
+ const [recurring, setRecurring] = useState(
+ subscription?.recurring ?? SubscriptionRecurring.Monthly
+ );
+
+ const currentPlan = subscription?.plan ?? SubscriptionPlan.Free;
+ const currentRecurring =
+ subscription?.recurring ?? SubscriptionRecurring.Monthly;
+
+ const refresh = useCallback(() => {
+ mutate();
+ }, [mutate]);
+
+ const yearlyDiscount = (
+ planDetail.get(SubscriptionPlan.Pro) as FixedPrice | undefined
+ )?.discount;
+
+ return (
+ <>
+
+ You are current on the {currentPlan} plan. If you have any
+ questions, please contact our{' '}
+ {/*TODO: add action*/}customer support .
+
+ }
+ />
+
+
+ {Object.values(SubscriptionRecurring).map(plan => (
+
+ {plan}
+ {plan === SubscriptionRecurring.Yearly && yearlyDiscount && (
+
+ {yearlyDiscount}% off
+
+ )}
+
+ ))}
+
+ {/* TODO: plan cards horizontal scroll behavior is not the same as design */}
+ {/* TODO: may scroll current plan into view when first loading? */}
+
+ {Array.from(planDetail.values()).map(detail => {
+ const isCurrent =
+ currentPlan === detail.plan && currentRecurring === recurring;
+ return (
+
+
+
+ {detail.plan}{' '}
+ {'discount' in detail && (
+
+ {detail.discount}% off
+
+ )}
+
+
+
+ $
+ {detail.type === 'dynamic'
+ ? '?'
+ : recurring === SubscriptionRecurring.Monthly
+ ? detail.price
+ : detail.yearlyPrice}
+
+ per month
+
+ {
+ // branches:
+ // if contact => 'Contact Sales'
+ // if not signed in:
+ // if free => 'Sign up free'
+ // else => 'Buy Pro'
+ // else
+ // if isCurrent => 'Current Plan'
+ // else if free => 'Downgrade'
+ // else if currentRecurring !== recurring => 'Change to {recurring} Billing'
+ // else => 'Upgrade'
+ // TODO: should replace with components with proper actions
+ detail.type === 'dynamic' ? (
+
+ ) : loggedIn ? (
+ isCurrent ? (
+
+ ) : detail.plan === SubscriptionPlan.Free ? (
+
+ ) : currentRecurring !== recurring ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ {detail.plan === SubscriptionPlan.Free
+ ? 'Sign up free'
+ : 'Buy Pro'}
+
+ )
+ }
+
+
+ {detail.benefits.map((content, i) => (
+
+
+ {/* TODO: icons */}
+ {detail.type == 'dynamic' ? '·' : '✅'}
+
+ {content}
+
+ ))}
+
+
+ );
+ })}
+
+
+ See all plans →{/* TODO: icon */}
+
+
+ >
+ );
+};
+
+const Downgrade = ({ onActionDone }: { onActionDone: () => void }) => {
+ const { isMutating, trigger } = useMutation({
+ mutation: cancelSubscriptionMutation,
+ });
+
+ const downgrade = useCallback(() => {
+ trigger(null, { onSuccess: onActionDone });
+ }, [trigger, onActionDone]);
+
+ return (
+
+ Downgrade
+
+ );
+};
+
+const Upgrade = ({
+ recurring,
+ onActionDone,
+}: {
+ recurring: SubscriptionRecurring;
+ onActionDone: () => void;
+}) => {
+ const { isMutating, trigger, data } = useMutation({
+ mutation: checkoutMutation,
+ });
+
+ const upgrade = useCallback(() => {
+ trigger({ recurring });
+ }, [trigger, recurring]);
+
+ const newTabRef = useRef(null);
+
+ useEffect(() => {
+ if (data?.checkout) {
+ if (newTabRef.current) {
+ newTabRef.current.focus();
+ } else {
+ // FIXME: safari prevents from opening new tab by window api
+ // TODO(@xp): what if electron?
+ const newTab = window.open(
+ data.checkout,
+ '_blank',
+ 'noopener noreferrer'
+ );
+
+ if (newTab) {
+ newTabRef.current = newTab;
+ const update = () => {
+ onActionDone();
+ };
+ newTab.addEventListener('close', update);
+
+ return () => newTab.removeEventListener('close', update);
+ }
+ }
+ }
+
+ return;
+ }, [data?.checkout, onActionDone]);
+
+ return (
+
+ Upgrade
+
+ );
+};
+
+const ChangeRecurring = ({
+ from: _from /* TODO: from can be useful when showing confirmation modal */,
+ to,
+ onActionDone,
+}: {
+ from: SubscriptionRecurring;
+ to: SubscriptionRecurring;
+ onActionDone: () => void;
+}) => {
+ const { isMutating, trigger } = useMutation({
+ mutation: updateSubscriptionMutation,
+ });
+
+ const change = useCallback(() => {
+ trigger({ recurring: to }, { onSuccess: onActionDone });
+ }, [trigger, onActionDone, to]);
+
+ return (
+
+ Change to {to} Billing
+
+ );
+};
+
+const ContactSales = () => {
+ return (
+ // TODO: add action
+
+ Contact Sales
+
+ );
+};
+
+const CurrentPlan = () => {
+ return Current Plan ;
+};
+
+const SignupAction = ({ children }: PropsWithChildren) => {
+ // TODO: add login action
+ return (
+
+ {children}
+
+ );
+};
+
+export const AFFiNECloudPlans = () => {
+ return (
+ // TODO: loading skeleton
+ // TODO: Error Boundary
+
+
+
+ );
+};
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts
new file mode 100644
index 0000000000..d12bb790eb
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts
@@ -0,0 +1,104 @@
+import { style } from '@vanilla-extract/css';
+
+export const wrapper = style({
+ width: '100%',
+});
+export const recurringRadioGroup = style({
+ width: '256px',
+});
+
+export const radioButtonDiscount = style({
+ marginLeft: '4px',
+ color: 'var(--affine-primary-color)',
+});
+
+export const planCardsWrapper = style({
+ marginTop: '24px',
+ display: 'flex',
+ overflowX: 'auto',
+});
+
+export const planCard = style({
+ minHeight: '426px',
+ minWidth: '258px',
+ borderRadius: '16px',
+ padding: '20px',
+ border: '1px solid var(--affine-border-color)',
+
+ selectors: {
+ '&:not(:last-child)': {
+ marginRight: '16px',
+ },
+ },
+});
+
+export const currentPlanCard = style([
+ planCard,
+ {
+ borderWidth: '2px',
+ borderColor: 'var(--affine-primary-color)',
+ },
+]);
+
+export const discountLabel = style({
+ color: 'var(--affine-primary-color)',
+ marginLeft: '8px',
+ lineHeight: '20px',
+ fontSize: 'var(--affine-font-xs)',
+ fontWeight: 500,
+ padding: '0 4px',
+ backgroundColor: 'var(--affine-blue-50)',
+ borderRadius: '4px',
+ display: 'inline-block',
+ height: '100%',
+});
+
+export const planTitle = style({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ gap: '10px',
+ fontWeight: 600,
+});
+
+export const planPrice = style({
+ fontSize: 'var(--affine-font-h-5)',
+ marginRight: '8px',
+});
+
+export const planPriceDesc = style({
+ color: 'var(--affine-text-secondary-color)',
+ fontSize: 'var(--affine-font-sm)',
+});
+
+export const planAction = style({
+ width: '100%',
+});
+
+export const planBenefits = style({
+ marginTop: '20px',
+ fontSize: 'var(--affine-font-xs)',
+});
+
+export const planBenefit = style({
+ display: 'flex',
+ selectors: {
+ '&:not(:last-child)': {
+ marginBottom: '8px',
+ },
+ },
+});
+
+export const planBenefitIcon = style({
+ display: 'inline-block',
+ marginRight: '8px',
+});
+
+export const allPlansLink = style({
+ display: 'block',
+ marginTop: '36px',
+ color: 'var(--affine-primary-color)',
+ background: 'transparent',
+ borderColor: 'transparent',
+ fontSize: 'var(--affine-font-xs)',
+});
diff --git a/packages/frontend/graphql/src/graphql/cancel-subscription.gql b/packages/frontend/graphql/src/graphql/cancel-subscription.gql
new file mode 100644
index 0000000000..128e206781
--- /dev/null
+++ b/packages/frontend/graphql/src/graphql/cancel-subscription.gql
@@ -0,0 +1,8 @@
+mutation cancelSubscription {
+ cancelSubscription {
+ id
+ status
+ nextBillAt
+ canceledAt
+ }
+}
diff --git a/packages/frontend/graphql/src/graphql/create-checkout-link.gql b/packages/frontend/graphql/src/graphql/create-checkout-link.gql
new file mode 100644
index 0000000000..b4e4704e97
--- /dev/null
+++ b/packages/frontend/graphql/src/graphql/create-checkout-link.gql
@@ -0,0 +1,3 @@
+mutation checkout($recurring: SubscriptionRecurring!) {
+ checkout(recurring: $recurring)
+}
diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts
index f31b3c5c8c..29330c8fed 100644
--- a/packages/frontend/graphql/src/graphql/index.ts
+++ b/packages/frontend/graphql/src/graphql/index.ts
@@ -79,6 +79,22 @@ query allBlobSizes {
}`,
};
+export const cancelSubscriptionMutation = {
+ id: 'cancelSubscriptionMutation' as const,
+ operationName: 'cancelSubscription',
+ definitionName: 'cancelSubscription',
+ containsFile: false,
+ query: `
+mutation cancelSubscription {
+ cancelSubscription {
+ id
+ status
+ nextBillAt
+ canceledAt
+ }
+}`,
+};
+
export const changeEmailMutation = {
id: 'changeEmailMutation' as const,
operationName: 'changeEmail',
@@ -111,6 +127,17 @@ mutation changePassword($token: String!, $newPassword: String!) {
}`,
};
+export const checkoutMutation = {
+ id: 'checkoutMutation' as const,
+ operationName: 'checkout',
+ definitionName: 'checkout',
+ containsFile: false,
+ query: `
+mutation checkout($recurring: SubscriptionRecurring!) {
+ checkout(recurring: $recurring)
+}`,
+};
+
export const createWorkspaceMutation = {
id: 'createWorkspaceMutation' as const,
operationName: 'createWorkspace',
@@ -321,6 +348,29 @@ query getWorkspaces {
}`,
};
+export const invoicesQuery = {
+ id: 'invoicesQuery' as const,
+ operationName: 'invoices',
+ definitionName: 'currentUser',
+ containsFile: false,
+ query: `
+query invoices($take: Int!, $skip: Int!) {
+ currentUser {
+ invoices(take: $take, skip: $skip) {
+ id
+ status
+ plan
+ recurring
+ currency
+ amount
+ reason
+ lastPaymentError
+ createdAt
+ }
+ }
+}`,
+};
+
export const leaveWorkspaceMutation = {
id: 'leaveWorkspaceMutation' as const,
operationName: 'leaveWorkspace',
@@ -336,6 +386,23 @@ mutation leaveWorkspace($workspaceId: String!, $workspaceName: String!, $sendLea
}`,
};
+export const pricesQuery = {
+ id: 'pricesQuery' as const,
+ operationName: 'prices',
+ definitionName: 'prices',
+ containsFile: false,
+ query: `
+query prices {
+ prices {
+ type
+ plan
+ currency
+ amount
+ yearlyAmount
+ }
+}`,
+};
+
export const removeAvatarMutation = {
id: 'removeAvatarMutation' as const,
operationName: 'removeAvatar',
@@ -469,6 +536,44 @@ mutation signUp($name: String!, $email: String!, $password: String!) {
}`,
};
+export const subscriptionQuery = {
+ id: 'subscriptionQuery' as const,
+ operationName: 'subscription',
+ definitionName: 'currentUser',
+ containsFile: false,
+ query: `
+query subscription {
+ currentUser {
+ subscription {
+ id
+ status
+ plan
+ recurring
+ start
+ end
+ nextBillAt
+ canceledAt
+ }
+ }
+}`,
+};
+
+export const updateSubscriptionMutation = {
+ id: 'updateSubscriptionMutation' as const,
+ operationName: 'updateSubscription',
+ definitionName: 'updateSubscriptionRecurring',
+ containsFile: false,
+ query: `
+mutation updateSubscription($recurring: SubscriptionRecurring!) {
+ updateSubscriptionRecurring(recurring: $recurring) {
+ id
+ plan
+ recurring
+ nextBillAt
+ }
+}`,
+};
+
export const uploadAvatarMutation = {
id: 'uploadAvatarMutation' as const,
operationName: 'uploadAvatar',
diff --git a/packages/frontend/graphql/src/graphql/invoices.gql b/packages/frontend/graphql/src/graphql/invoices.gql
new file mode 100644
index 0000000000..569b77730f
--- /dev/null
+++ b/packages/frontend/graphql/src/graphql/invoices.gql
@@ -0,0 +1,15 @@
+query invoices($take: Int!, $skip: Int!) {
+ currentUser {
+ invoices(take: $take, skip: $skip) {
+ id
+ status
+ plan
+ recurring
+ currency
+ amount
+ reason
+ lastPaymentError
+ createdAt
+ }
+ }
+}
diff --git a/packages/frontend/graphql/src/graphql/prices.gql b/packages/frontend/graphql/src/graphql/prices.gql
new file mode 100644
index 0000000000..eb4a75e7bd
--- /dev/null
+++ b/packages/frontend/graphql/src/graphql/prices.gql
@@ -0,0 +1,9 @@
+query prices {
+ prices {
+ type
+ plan
+ currency
+ amount
+ yearlyAmount
+ }
+}
diff --git a/packages/frontend/graphql/src/graphql/subscription.gql b/packages/frontend/graphql/src/graphql/subscription.gql
new file mode 100644
index 0000000000..0072350f1f
--- /dev/null
+++ b/packages/frontend/graphql/src/graphql/subscription.gql
@@ -0,0 +1,14 @@
+query subscription {
+ currentUser {
+ subscription {
+ id
+ status
+ plan
+ recurring
+ start
+ end
+ nextBillAt
+ canceledAt
+ }
+ }
+}
diff --git a/packages/frontend/graphql/src/graphql/update-subscription-billing.gql b/packages/frontend/graphql/src/graphql/update-subscription-billing.gql
new file mode 100644
index 0000000000..1464d399d6
--- /dev/null
+++ b/packages/frontend/graphql/src/graphql/update-subscription-billing.gql
@@ -0,0 +1,8 @@
+mutation updateSubscription($recurring: SubscriptionRecurring!) {
+ updateSubscriptionRecurring(recurring: $recurring) {
+ id
+ plan
+ recurring
+ nextBillAt
+ }
+}
diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts
index 3a84e42fe4..e38e9b760c 100644
--- a/packages/frontend/graphql/src/schema.ts
+++ b/packages/frontend/graphql/src/schema.ts
@@ -130,6 +130,21 @@ export type AllBlobSizesQuery = {
collectAllBlobSizes: { __typename?: 'WorkspaceBlobSizes'; size: number };
};
+export type CancelSubscriptionMutationVariables = Exact<{
+ [key: string]: never;
+}>;
+
+export type CancelSubscriptionMutation = {
+ __typename?: 'Mutation';
+ cancelSubscription: {
+ __typename?: 'UserSubscription';
+ id: string;
+ status: SubscriptionStatus;
+ nextBillAt: string | null;
+ canceledAt: string | null;
+ };
+};
+
export type ChangeEmailMutationVariables = Exact<{
token: Scalars['String']['input'];
}>;
@@ -161,6 +176,12 @@ export type ChangePasswordMutation = {
};
};
+export type CheckoutMutationVariables = Exact<{
+ recurring: SubscriptionRecurring;
+}>;
+
+export type CheckoutMutation = { __typename?: 'Mutation'; checkout: string };
+
export type CreateWorkspaceMutationVariables = Exact<{
init: Scalars['Upload']['input'];
}>;
@@ -328,6 +349,30 @@ export type GetWorkspacesQuery = {
workspaces: Array<{ __typename?: 'WorkspaceType'; id: string }>;
};
+export type InvoicesQueryVariables = Exact<{
+ take: Scalars['Int']['input'];
+ skip: Scalars['Int']['input'];
+}>;
+
+export type InvoicesQuery = {
+ __typename?: 'Query';
+ currentUser: {
+ __typename?: 'UserType';
+ invoices: Array<{
+ __typename?: 'UserInvoice';
+ id: string;
+ status: InvoiceStatus;
+ plan: SubscriptionPlan;
+ recurring: SubscriptionRecurring;
+ currency: string;
+ amount: number;
+ reason: string;
+ lastPaymentError: string | null;
+ createdAt: string;
+ }>;
+ } | null;
+};
+
export type LeaveWorkspaceMutationVariables = Exact<{
workspaceId: Scalars['String']['input'];
workspaceName: Scalars['String']['input'];
@@ -339,6 +384,20 @@ export type LeaveWorkspaceMutation = {
leaveWorkspace: boolean;
};
+export type PricesQueryVariables = Exact<{ [key: string]: never }>;
+
+export type PricesQuery = {
+ __typename?: 'Query';
+ prices: Array<{
+ __typename?: 'SubscriptionPrice';
+ type: string;
+ plan: SubscriptionPlan;
+ currency: string;
+ amount: number;
+ yearlyAmount: number;
+ }>;
+};
+
export type RemoveAvatarMutationVariables = Exact<{ [key: string]: never }>;
export type RemoveAvatarMutation = {
@@ -451,6 +510,41 @@ export type SignUpMutation = {
};
};
+export type SubscriptionQueryVariables = Exact<{ [key: string]: never }>;
+
+export type SubscriptionQuery = {
+ __typename?: 'Query';
+ currentUser: {
+ __typename?: 'UserType';
+ subscription: {
+ __typename?: 'UserSubscription';
+ id: string;
+ status: SubscriptionStatus;
+ plan: SubscriptionPlan;
+ recurring: SubscriptionRecurring;
+ start: string;
+ end: string;
+ nextBillAt: string | null;
+ canceledAt: string | null;
+ } | null;
+ } | null;
+};
+
+export type UpdateSubscriptionMutationVariables = Exact<{
+ recurring: SubscriptionRecurring;
+}>;
+
+export type UpdateSubscriptionMutation = {
+ __typename?: 'Mutation';
+ updateSubscriptionRecurring: {
+ __typename?: 'UserSubscription';
+ id: string;
+ plan: SubscriptionPlan;
+ recurring: SubscriptionRecurring;
+ nextBillAt: string | null;
+ };
+};
+
export type UploadAvatarMutationVariables = Exact<{
avatar: Scalars['Upload']['input'];
}>;
@@ -570,6 +664,21 @@ export type Queries =
name: 'getWorkspacesQuery';
variables: GetWorkspacesQueryVariables;
response: GetWorkspacesQuery;
+ }
+ | {
+ name: 'invoicesQuery';
+ variables: InvoicesQueryVariables;
+ response: InvoicesQuery;
+ }
+ | {
+ name: 'pricesQuery';
+ variables: PricesQueryVariables;
+ response: PricesQuery;
+ }
+ | {
+ name: 'subscriptionQuery';
+ variables: SubscriptionQueryVariables;
+ response: SubscriptionQuery;
};
export type Mutations =
@@ -583,6 +692,11 @@ export type Mutations =
variables: SetBlobMutationVariables;
response: SetBlobMutation;
}
+ | {
+ name: 'cancelSubscriptionMutation';
+ variables: CancelSubscriptionMutationVariables;
+ response: CancelSubscriptionMutation;
+ }
| {
name: 'changeEmailMutation';
variables: ChangeEmailMutationVariables;
@@ -593,6 +707,11 @@ export type Mutations =
variables: ChangePasswordMutationVariables;
response: ChangePasswordMutation;
}
+ | {
+ name: 'checkoutMutation';
+ variables: CheckoutMutationVariables;
+ response: CheckoutMutation;
+ }
| {
name: 'createWorkspaceMutation';
variables: CreateWorkspaceMutationVariables;
@@ -668,6 +787,11 @@ export type Mutations =
variables: SignUpMutationVariables;
response: SignUpMutation;
}
+ | {
+ name: 'updateSubscriptionMutation';
+ variables: UpdateSubscriptionMutationVariables;
+ response: UpdateSubscriptionMutation;
+ }
| {
name: 'uploadAvatarMutation';
variables: UploadAvatarMutationVariables;