Skip to content

Commit

Permalink
fix: prevent billingPortal creation if no active subscription (#9701)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: Charles Bochet <[email protected]>
  • Loading branch information
3 people authored Jan 21, 2025
1 parent 47c2c77 commit d8815d7
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 270 deletions.
95 changes: 1 addition & 94 deletions packages/twenty-front/src/generated-metadata/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']>;
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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'];
Expand All @@ -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',
Expand Down Expand Up @@ -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'];
Expand Down
105 changes: 8 additions & 97 deletions packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
@@ -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] };
Expand Down Expand Up @@ -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']>;
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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'];
Expand All @@ -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',
Expand Down Expand Up @@ -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'];
Expand Down Expand Up @@ -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; }>;

Expand All @@ -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'];
Expand Down Expand Up @@ -2504,6 +2411,10 @@ export const UserQueryFragmentFragmentDoc = gql`
status
interval
}
billingSubscriptions {
id
status
}
workspaceMembersCount
}
workspaces {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type CurrentWorkspace = Pick<
| 'allowImpersonation'
| 'featureFlags'
| 'activationStatus'
| 'billingSubscriptions'
| 'currentBillingSubscription'
| 'workspaceMembersCount'
| 'isPublicInviteLinkEnabled'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ export const queries = {
status
interval
}
billingSubscriptions {
id
status
}
workspaceMembersCount
}
workspaces {
Expand Down Expand Up @@ -300,6 +304,8 @@ export const responseData = {
currentBillingSubscription: null,
workspaceMembersCount: 1,
},
currentBillingSubscription: null,
billingSubscriptions: [],
workspaces: [],
userVars: null,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
},
],
});
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export const USER_QUERY_FRAGMENT = gql`
status
interval
}
billingSubscriptions {
id
status
}
workspaceMembersCount
}
workspaces {
Expand Down
Loading

0 comments on commit d8815d7

Please sign in to comment.