diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx
index f3668ff0b..4f1267000 100644
--- a/packages/twenty-front/src/App.tsx
+++ b/packages/twenty-front/src/App.tsx
@@ -3,7 +3,6 @@ import {
createBrowserRouter,
createRoutesFromElements,
Outlet,
- redirect,
Route,
RouterProvider,
Routes,
@@ -192,14 +191,12 @@ const createRouter = (
path={SettingsPath.AccountsEmails}
element={}
/>
- }
- loader={() => {
- if (!isBillingEnabled) return redirect(AppPath.Index);
- return null;
- }}
- />
+ {isBillingEnabled && (
+ }
+ />
+ )}
}
@@ -324,15 +321,19 @@ const createRouter = (
export const App = () => {
const billing = useRecoilValue(billingState);
+ const isFreeAccessEnabled = useIsFeatureEnabled('IS_FREE_ACCESS_ENABLED');
const isCRMMigrationEnabled = useIsFeatureEnabled('IS_CRM_MIGRATION_ENABLED');
const isServerlessFunctionSettingsEnabled = useIsFeatureEnabled(
'IS_FUNCTION_SETTINGS_ENABLED',
);
+ const isBillingPageEnabled =
+ billing?.isBillingEnabled && !isFreeAccessEnabled;
+
return (
;
+ /** Version of the serverless function to execute */
+ version?: Scalars['String'];
+};
+
export type FeatureFlag = {
__typename?: 'FeatureFlag';
id: Scalars['UUID'];
@@ -287,6 +296,13 @@ export type FullName = {
lastName: Scalars['String'];
};
+export type GetServerlessFunctionSourceCodeInput = {
+ /** The id of the function. */
+ id: Scalars['ID'];
+ /** The version of the function */
+ version?: Scalars['String'];
+};
+
export type InvalidatePassword = {
__typename?: 'InvalidatePassword';
/** Boolean that confirms query was dispatched */
@@ -343,8 +359,9 @@ export type Mutation = {
generateJWT: AuthTokens;
generateTransientToken: TransientToken;
impersonate: Verify;
+ publishServerlessFunction: ServerlessFunction;
renewToken: AuthTokens;
- runWorkflowVersion: WorkflowTriggerResult;
+ runWorkflowVersion: WorkflowRun;
sendInviteLink: SendInviteLink;
signUp: LoginToken;
skipSyncEmailOnboardingStep: OnboardingStepSuccess;
@@ -431,8 +448,7 @@ export type MutationExchangeAuthorizationCodeArgs = {
export type MutationExecuteOneServerlessFunctionArgs = {
- id: Scalars['UUID'];
- payload?: InputMaybe;
+ input: ExecuteServerlessFunctionInput;
};
@@ -452,6 +468,11 @@ export type MutationImpersonateArgs = {
};
+export type MutationPublishServerlessFunctionArgs = {
+ input: PublishServerlessFunctionInput;
+};
+
+
export type MutationRenewTokenArgs = {
appToken: Scalars['String'];
};
@@ -594,6 +615,11 @@ export type ProductPricesEntity = {
totalNumberOfPrices: Scalars['Int'];
};
+export type PublishServerlessFunctionInput = {
+ /** The id of the function. */
+ id: Scalars['ID'];
+};
+
export type Query = {
__typename?: 'Query';
billingPortalSession: SessionEntity;
@@ -606,6 +632,7 @@ export type Query = {
getAISQLQuery: AisqlQueryResult;
getPostgresCredentials?: Maybe;
getProductPrices: ProductPricesEntity;
+ getServerlessFunctionSourceCode: Scalars['String'];
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal;
getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal;
@@ -649,6 +676,11 @@ export type QueryGetProductPricesArgs = {
};
+export type QueryGetServerlessFunctionSourceCodeArgs = {
+ input: GetServerlessFunctionSourceCodeInput;
+};
+
+
export type QueryGetTimelineCalendarEventsFromCompanyIdArgs = {
companyId: Scalars['UUID'];
page: Scalars['Int'];
@@ -768,9 +800,9 @@ export type ServerlessFunction = {
createdAt: Scalars['DateTime'];
description?: Maybe;
id: Scalars['UUID'];
+ latestVersion?: Maybe;
name: Scalars['String'];
runtime: Scalars['String'];
- sourceCodeFullPath: Scalars['String'];
sourceCodeHash: Scalars['String'];
syncStatus: ServerlessFunctionSyncStatus;
updatedAt: Scalars['DateTime'];
@@ -1054,10 +1086,9 @@ export type Verify = {
user: User;
};
-export type WorkflowTriggerResult = {
- __typename?: 'WorkflowTriggerResult';
- /** Execution result in JSON format */
- result?: Maybe;
+export type WorkflowRun = {
+ __typename?: 'WorkflowRun';
+ workflowRunId: Scalars['UUID'];
};
export type Workspace = {
diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx
index f68981a19..89bb9c0de 100644
--- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx
+++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx
@@ -33,7 +33,10 @@ export const SettingsNavigationDrawerItems = () => {
const isFunctionSettingsEnabled = useIsFeatureEnabled(
'IS_FUNCTION_SETTINGS_ENABLED',
);
+ const isFreeAccessEnabled = useIsFeatureEnabled('IS_FREE_ACCESS_ENABLED');
const isCRMMigrationEnabled = useIsFeatureEnabled('IS_CRM_MIGRATION_ENABLED');
+ const isBillingPageEnabled =
+ billing?.isBillingEnabled && !isFreeAccessEnabled;
return (
<>
@@ -84,7 +87,7 @@ export const SettingsNavigationDrawerItems = () => {
path={SettingsPath.WorkspaceMembersPage}
Icon={IconUsers}
/>
- {billing?.isBillingEnabled && (
+ {isBillingPageEnabled && (
{
);
useGetOneServerlessFunctionMock.useGetOneServerlessFunction.mockReturnValue(
{
- serverlessFunction: { sourceCodeFullPath: undefined },
+ serverlessFunction: { name: 'name' },
},
);
const useGetOneServerlessFunctionSourceCodeMock = jest.requireMock(
diff --git a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts
index 1185aa04f..6ed0dd7f1 100644
--- a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts
+++ b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts
@@ -7,4 +7,5 @@ export type FeatureFlagKey =
| 'IS_FUNCTION_SETTINGS_ENABLED'
| 'IS_COPILOT_ENABLED'
| 'IS_CRM_MIGRATION_ENABLED'
+ | 'IS_FREE_ACCESS_ENABLED'
| 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED';
diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx
index 219f10e73..8543e0376 100644
--- a/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx
+++ b/packages/twenty-front/src/pages/settings/serverless-functions/__stories__/SettingsServerlessFunctionDetail.stories.tsx
@@ -38,7 +38,6 @@ const meta: Meta = {
description: '',
syncStatus: 'READY',
runtime: 'nodejs18.x',
- sourceCodeFullPath: SOURCE_CODE_FULL_PATH,
sourceCodeHash: '42d2734b3dc8a7b45a16803ed7f417bc',
updatedAt: '2024-02-24T10:23:10.673Z',
createdAt: '2024-02-24T10:23:10.673Z',
diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts
index a1bf997b8..a60992cc7 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts
@@ -43,7 +43,7 @@ export class BillingResolver {
@Args() { returnUrlPath }: BillingSessionInput,
) {
return {
- url: await this.billingPortalWorkspaceService.computeBillingPortalSessionURL(
+ url: await this.billingPortalWorkspaceService.computeBillingPortalSessionURLOrThrow(
user.defaultWorkspaceId,
returnUrlPath,
),
diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts
index ca4685f07..d4a7a333f 100644
--- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts
+++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts
@@ -65,18 +65,22 @@ export class BillingPortalWorkspaceService {
return session.url;
}
- async computeBillingPortalSessionURL(
+ async computeBillingPortalSessionURLOrThrow(
workspaceId: string,
returnUrlPath?: string,
) {
- const currentSubscriptionItem =
+ const currentSubscription =
await this.billingSubscriptionService.getCurrentBillingSubscriptionOrThrow(
{
workspaceId,
},
);
- const stripeCustomerId = currentSubscriptionItem.stripeCustomerId;
+ if (!currentSubscription) {
+ throw new Error('Error: missing subscription');
+ }
+
+ const stripeCustomerId = currentSubscription.stripeCustomerId;
if (!stripeCustomerId) {
throw new Error('Error: missing stripeCustomerId');