fix: prevent billingPortal creation if no active subscription (#9701)

Billing portal is created in settings/billing page even if subscription
is canceled, causing server internal error. -> Skip back end request

Bonus : display settings/billing page with disabled button even if
subscription is canceled

---------

Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Etienne
2025-01-21 15:01:18 +01:00
committed by GitHub
parent 47c2c774e3
commit d8815d7ebf
12 changed files with 123 additions and 270 deletions

View File

@ -135,22 +135,6 @@ export type BillingSubscription = {
status: SubscriptionStatus;
};
export type BillingSubscriptionFilter = {
and?: InputMaybe<Array<BillingSubscriptionFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<BillingSubscriptionFilter>>;
};
export type BillingSubscriptionSort = {
direction: SortDirection;
field: BillingSubscriptionSortFields;
nulls?: InputMaybe<SortNulls>;
};
export enum BillingSubscriptionSortFields {
id = 'id'
}
export type BooleanFieldComparison = {
is?: InputMaybe<Scalars['Boolean']['input']>;
isNot?: InputMaybe<Scalars['Boolean']['input']>;
@ -1418,18 +1402,6 @@ export type SignUpOutput = {
workspace: WorkspaceSubdomainAndId;
};
/** Sort Directions */
export enum SortDirection {
ASC = 'ASC',
DESC = 'DESC'
}
/** Sort Nulls Options */
export enum SortNulls {
NULLS_FIRST = 'NULLS_FIRST',
NULLS_LAST = 'NULLS_LAST'
}
export enum SubscriptionInterval {
Day = 'Day',
Month = 'Month',
@ -1743,9 +1715,7 @@ export type Workspace = {
__typename?: 'Workspace';
activationStatus: WorkspaceActivationStatus;
allowImpersonation: Scalars['Boolean']['output'];
billingCustomers?: Maybe<Array<BillingCustomer>>;
billingEntitlements?: Maybe<Array<BillingEntitlement>>;
billingSubscriptions?: Maybe<Array<BillingSubscription>>;
billingSubscriptions: Array<BillingSubscription>;
createdAt: Scalars['DateTime']['output'];
currentBillingSubscription?: Maybe<BillingSubscription>;
databaseSchema: Scalars['String']['output'];
@ -1768,24 +1738,6 @@ export type Workspace = {
workspaceMembersCount?: Maybe<Scalars['Float']['output']>;
};
export type WorkspaceBillingCustomersArgs = {
filter?: BillingCustomerFilter;
sorting?: Array<BillingCustomerSort>;
};
export type WorkspaceBillingEntitlementsArgs = {
filter?: BillingEntitlementFilter;
sorting?: Array<BillingEntitlementSort>;
};
export type WorkspaceBillingSubscriptionsArgs = {
filter?: BillingSubscriptionFilter;
sorting?: Array<BillingSubscriptionSort>;
};
export enum WorkspaceActivationStatus {
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
@ -1864,51 +1816,6 @@ export type WorkspaceSubdomainAndId = {
subdomain: Scalars['String']['output'];
};
export type BillingCustomer = {
__typename?: 'billingCustomer';
id: Scalars['UUID']['output'];
};
export type BillingCustomerFilter = {
and?: InputMaybe<Array<BillingCustomerFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<BillingCustomerFilter>>;
};
export type BillingCustomerSort = {
direction: SortDirection;
field: BillingCustomerSortFields;
nulls?: InputMaybe<SortNulls>;
};
export enum BillingCustomerSortFields {
id = 'id'
}
export type BillingEntitlement = {
__typename?: 'billingEntitlement';
id: Scalars['UUID']['output'];
key: Scalars['String']['output'];
value: Scalars['Boolean']['output'];
workspaceId: Scalars['String']['output'];
};
export type BillingEntitlementFilter = {
and?: InputMaybe<Array<BillingEntitlementFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<BillingEntitlementFilter>>;
};
export type BillingEntitlementSort = {
direction: SortDirection;
field: BillingEntitlementSortFields;
nulls?: InputMaybe<SortNulls>;
};
export enum BillingEntitlementSortFields {
id = 'id'
}
export type Field = {
__typename?: 'field';
createdAt: Scalars['DateTime']['output'];

View File

@ -1,5 +1,5 @@
import * as Apollo from '@apollo/client';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
@ -128,22 +128,6 @@ export type BillingSubscription = {
status: SubscriptionStatus;
};
export type BillingSubscriptionFilter = {
and?: InputMaybe<Array<BillingSubscriptionFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<BillingSubscriptionFilter>>;
};
export type BillingSubscriptionSort = {
direction: SortDirection;
field: BillingSubscriptionSortFields;
nulls?: InputMaybe<SortNulls>;
};
export enum BillingSubscriptionSortFields {
id = 'id'
}
export type BooleanFieldComparison = {
is?: InputMaybe<Scalars['Boolean']>;
isNot?: InputMaybe<Scalars['Boolean']>;
@ -1228,18 +1212,6 @@ export type SignUpOutput = {
workspace: WorkspaceSubdomainAndId;
};
/** Sort Directions */
export enum SortDirection {
ASC = 'ASC',
DESC = 'DESC'
}
/** Sort Nulls Options */
export enum SortNulls {
NULLS_FIRST = 'NULLS_FIRST',
NULLS_LAST = 'NULLS_LAST'
}
export enum SubscriptionInterval {
Day = 'Day',
Month = 'Month',
@ -1540,9 +1512,7 @@ export type Workspace = {
__typename?: 'Workspace';
activationStatus: WorkspaceActivationStatus;
allowImpersonation: Scalars['Boolean'];
billingCustomers?: Maybe<Array<BillingCustomer>>;
billingEntitlements?: Maybe<Array<BillingEntitlement>>;
billingSubscriptions?: Maybe<Array<BillingSubscription>>;
billingSubscriptions: Array<BillingSubscription>;
createdAt: Scalars['DateTime'];
currentBillingSubscription?: Maybe<BillingSubscription>;
databaseSchema: Scalars['String'];
@ -1565,24 +1535,6 @@ export type Workspace = {
workspaceMembersCount?: Maybe<Scalars['Float']>;
};
export type WorkspaceBillingCustomersArgs = {
filter?: BillingCustomerFilter;
sorting?: Array<BillingCustomerSort>;
};
export type WorkspaceBillingEntitlementsArgs = {
filter?: BillingEntitlementFilter;
sorting?: Array<BillingEntitlementSort>;
};
export type WorkspaceBillingSubscriptionsArgs = {
filter?: BillingSubscriptionFilter;
sorting?: Array<BillingSubscriptionSort>;
};
export enum WorkspaceActivationStatus {
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
@ -1661,51 +1613,6 @@ export type WorkspaceSubdomainAndId = {
subdomain: Scalars['String'];
};
export type BillingCustomer = {
__typename?: 'billingCustomer';
id: Scalars['UUID'];
};
export type BillingCustomerFilter = {
and?: InputMaybe<Array<BillingCustomerFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<BillingCustomerFilter>>;
};
export type BillingCustomerSort = {
direction: SortDirection;
field: BillingCustomerSortFields;
nulls?: InputMaybe<SortNulls>;
};
export enum BillingCustomerSortFields {
id = 'id'
}
export type BillingEntitlement = {
__typename?: 'billingEntitlement';
id: Scalars['UUID'];
key: Scalars['String'];
value: Scalars['Boolean'];
workspaceId: Scalars['String'];
};
export type BillingEntitlementFilter = {
and?: InputMaybe<Array<BillingEntitlementFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<BillingEntitlementFilter>>;
};
export type BillingEntitlementSort = {
direction: SortDirection;
field: BillingEntitlementSortFields;
nulls?: InputMaybe<SortNulls>;
};
export enum BillingEntitlementSortFields {
id = 'id'
}
export type Field = {
__typename?: 'field';
createdAt: Scalars['DateTime'];
@ -2191,7 +2098,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key:
export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> };
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
@ -2208,7 +2115,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> } };
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> } };
export type ActivateWorkflowVersionMutationVariables = Exact<{
workflowVersionId: Scalars['String'];
@ -2504,6 +2411,10 @@ export const UserQueryFragmentFragmentDoc = gql`
status
interval
}
billingSubscriptions {
id
status
}
workspaceMembersCount
}
workspaces {

View File

@ -11,6 +11,7 @@ export type CurrentWorkspace = Pick<
| 'allowImpersonation'
| 'featureFlags'
| 'activationStatus'
| 'billingSubscriptions'
| 'currentBillingSubscription'
| 'workspaceMembersCount'
| 'isPublicInviteLinkEnabled'

View File

@ -160,6 +160,10 @@ export const queries = {
status
interval
}
billingSubscriptions {
id
status
}
workspaceMembersCount
}
workspaces {
@ -300,6 +304,8 @@ export const responseData = {
currentBillingSubscription: null,
workspaceMembersCount: 1,
},
currentBillingSubscription: null,
billingSubscriptions: [],
workspaces: [],
userVars: null,
},

View File

@ -4,7 +4,11 @@ import { Nullable } from 'twenty-ui';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { WorkspaceActivationStatus } from '~/generated/graphql';
import {
SubscriptionInterval,
SubscriptionStatus,
WorkspaceActivationStatus,
} from '~/generated/graphql';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
@ -23,6 +27,17 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
isGoogleAuthEnabled: true,
isMicrosoftAuthEnabled: false,
isPasswordAuthEnabled: true,
currentBillingSubscription: {
id: '1',
interval: SubscriptionInterval.Month,
status: SubscriptionStatus.Active,
},
billingSubscriptions: [
{
id: '1',
status: SubscriptionStatus.Active,
},
],
});
},
});

View File

@ -50,6 +50,10 @@ export const USER_QUERY_FRAGMENT = gql`
status
interval
}
billingSubscriptions {
id
status
}
workspaceMembersCount
}
workspaces {

View File

@ -12,7 +12,6 @@ import {
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsBillingCoverImage } from '@/billing/components/SettingsBillingCoverImage';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
@ -21,8 +20,8 @@ import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModa
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import {
OnboardingStatus,
SubscriptionInterval,
SubscriptionStatus,
useBillingPortalSessionQuery,
useUpdateBillingSubscriptionMutation,
} from '~/generated/graphql';
@ -59,9 +58,16 @@ export const SettingsBilling = () => {
};
const { enqueueSnackBar } = useSnackBar();
const onboardingStatus = useOnboardingStatus();
const subscriptionStatus = useSubscriptionStatus();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const subscriptions = currentWorkspace?.billingSubscriptions;
const hasSubscriptions = (subscriptions?.length ?? 0) > 0;
const subscriptionStatus = useSubscriptionStatus();
const hasNotCanceledCurrentSubscription =
isDefined(subscriptionStatus) &&
subscriptionStatus !== SubscriptionStatus.Canceled;
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const switchingInfo =
currentWorkspace?.currentBillingSubscription?.interval ===
@ -75,18 +81,12 @@ export const SettingsBilling = () => {
variables: {
returnUrlPath: '/settings/billing',
},
skip: !hasSubscriptions,
});
const billingPortalButtonDisabled =
loading || !isDefined(data) || !isDefined(data.billingPortalSession.url);
const switchIntervalButtonDisabled =
onboardingStatus !== OnboardingStatus.COMPLETED;
const cancelPlanButtonDisabled =
billingPortalButtonDisabled ||
onboardingStatus !== OnboardingStatus.COMPLETED;
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
window.location.replace(data.billingPortalSession.url);
@ -137,50 +137,46 @@ export const SettingsBilling = () => {
>
<SettingsPageContainer>
<SettingsBillingCoverImage />
{isDefined(subscriptionStatus) && (
<>
<Section>
<H2Title
title={t`Manage your subscription`}
description={t`Edit payment method, see your invoices and more`}
/>
<Button
Icon={IconCreditCard}
title={t`View billing details`}
variant="secondary"
onClick={openBillingPortal}
disabled={billingPortalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title={t`Edit billing interval`}
description={t`Switch ${from}`}
/>
<Button
Icon={IconCalendarEvent}
title={t`Switch ${to}`}
variant="secondary"
onClick={openSwitchingIntervalModal}
disabled={switchIntervalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title={t`Cancel your subscription`}
description={t`Your workspace will be disabled`}
/>
<Button
Icon={IconCircleX}
title={t`Cancel Plan`}
variant="secondary"
accent="danger"
onClick={openBillingPortal}
disabled={cancelPlanButtonDisabled}
/>
</Section>
</>
)}
<Section>
<H2Title
title={t`Manage your subscription`}
description={t`Edit payment method, see your invoices and more`}
/>
<Button
Icon={IconCreditCard}
title={t`View billing details`}
variant="secondary"
onClick={openBillingPortal}
disabled={billingPortalButtonDisabled}
/>
</Section>
<Section>
<H2Title
title={t`Edit billing interval`}
description={t`Switch ${from}`}
/>
<Button
Icon={IconCalendarEvent}
title={t`Switch ${to}`}
variant="secondary"
onClick={openSwitchingIntervalModal}
disabled={!hasNotCanceledCurrentSubscription}
/>
</Section>
<Section>
<H2Title
title={t`Cancel your subscription`}
description={t`Your workspace will be disabled`}
/>
<Button
Icon={IconCircleX}
title={t`Cancel Plan`}
variant="secondary"
accent="danger"
onClick={openBillingPortal}
disabled={!hasNotCanceledCurrentSubscription}
/>
</Section>
</SettingsPageContainer>
<ConfirmationModal
isOpen={isSwitchingIntervalModalOpen}

View File

@ -73,6 +73,13 @@ export const mockCurrentWorkspace: Workspace = {
interval: SubscriptionInterval.Month,
status: SubscriptionStatus.Active,
},
billingSubscriptions: [
{
__typename: 'BillingSubscription',
id: '7efbc3f7-6e5e-4128-957e-8d86808cdf6a',
status: SubscriptionStatus.Active,
},
],
workspaceMembersCount: 1,
databaseSchema: '',
databaseUrl: '',