From 97f5a5b8a5863e0a4eb57e4d1ec04237da53d1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 31 Dec 2024 14:48:00 +0100 Subject: [PATCH] Pass Billing Checkout var in url to bypass credit card (#9283) --- README.md | 7 ++ packages/twenty-front/package.json | 1 + .../twenty-front/src/generated/graphql.tsx | 89 ++++++++++++++++++- .../right-drawer/states/copilotQueryState.ts | 2 +- .../states/lastViewableEmailThreadIdState.ts | 2 +- .../activityTargetableEntityArrayState.ts | 2 +- .../states/canCreateActivityState.ts | 2 +- .../states/isActivityInCreateModeState.ts | 2 +- .../states/isCreatingActivityInDBState.ts | 2 +- .../src/modules/app/components/App.tsx | 33 +++---- .../src/modules/auth/hooks/useAuth.ts | 16 +++- .../__tests__/useSignInWithGoogle.test.ts | 32 +++++-- .../__tests__/useSignInWithMicrosoft.test.ts | 26 +++++- .../sign-in-up/hooks/useSignInWithGoogle.ts | 14 ++- .../hooks/useSignInWithMicrosoft.ts | 5 ++ .../availableIdentityProviderForAuthState.ts | 2 +- .../states/billingCheckoutSessionState.ts | 39 ++++++++ .../modules/auth/states/currentUserState.ts | 2 +- .../states/currentWorkspaceMemberState.ts | 2 +- .../states/currentWorkspaceMembersStates.ts | 2 +- .../auth/states/currentWorkspaceState.ts | 2 +- .../auth/states/isCurrentUserLoadingState.ts | 2 +- .../auth/states/isVerifyPendingState.ts | 2 +- .../modules/auth/states/previousUrlState.ts | 2 +- .../modules/auth/states/signInUpModeState.ts | 2 +- .../modules/auth/states/signInUpStepState.ts | 2 +- .../src/modules/auth/states/tokenPairState.ts | 2 +- .../auth/states/workspacePublicDataState.ts | 2 +- .../src/modules/auth/states/workspaces.ts | 2 +- .../auth/types/billingCheckoutSession.type.ts | 9 ++ .../billing/graphql/checkoutSession.ts | 4 + .../captcha/states/captchaTokenState.ts | 2 +- .../states/isCaptchaScriptLoadedState.ts | 2 +- .../states/isRequestingCaptchaTokenState.ts | 2 +- .../isLoadingTokensFromExtensionState.ts | 2 +- .../client-config/states/apiConfigState.ts | 2 +- .../states/authProvidersState.ts | 2 +- .../client-config/states/billingState.ts | 2 +- .../states/captchaProviderState.ts | 2 +- .../states/chromeExtensionIdState.ts | 2 +- .../states/clientConfigApiStatusState.ts | 2 +- .../states/isAnalyticsEnabledState.ts | 2 +- .../client-config/states/isDebugModeState.ts | 2 +- .../isDeveloperDefaultSignInPrefilledState.ts | 2 +- .../states/isMultiWorkspaceEnabledState.ts | 2 +- .../client-config/states/isSSOEnabledState.ts | 2 +- .../client-config/states/sentryConfigState.ts | 2 +- .../client-config/states/supportChatState.ts | 2 +- .../states/commandMenuSearchState.ts | 2 +- .../mainContextStoreComponentInstanceId.ts | 2 +- .../states/domainConfigurationState.ts | 2 +- .../lastAuthenticatedWorkspaceDomainState.ts | 2 +- .../states/dateTimeFormatState.ts | 2 +- .../currentMobileNavigationDrawerState.ts | 2 +- ...isAppWaitingForFreshObjectMetadataState.ts | 2 +- .../states/objectMetadataItemsState.ts | 2 +- .../states/aggregateDropdownState.ts | 2 +- .../states/lastShowPageRecordId.ts | 2 +- .../recordIndexFieldDefinitionsState.ts | 2 +- .../states/recordIndexFiltersState.ts | 2 +- .../recordIndexIsCompactModeActiveState.ts | 2 +- ...ecordIndexKanbanAggregateOperationState.ts | 2 +- .../recordIndexKanbanFieldMetadataIdState.ts | 2 +- .../states/recordIndexSortsState.ts | 2 +- .../recordIndexViewFilterGroupsState.ts | 2 +- .../states/recordIndexViewTypeState.ts | 2 +- .../states/isNewViewableRecordLoading.ts | 2 +- .../states/viewableRecordIdState.ts | 2 +- .../states/viewableRecordNameSingularState.ts | 2 +- .../useUpsertTableRecordInGroup.test.tsx | 2 +- .../useUpsertTableRecordNoGroup.test.tsx | 2 +- .../states/isRemoveSortingModalOpenState.ts | 2 +- .../states/isSoftFocusUsingMouseState.ts | 2 +- .../states/settingsPreviewRecordIdState.ts | 2 +- .../states/generatedApiKeyTokenState.ts | 2 +- .../states/SSOIdentitiesProvidersState.ts | 2 +- .../states/spreadsheetImportDialogState.ts | 2 +- .../states/activeDropdownFocusIdState.ts | 2 +- .../states/previousDropdownFocusIdState.ts | 2 +- .../isRightDrawerAnimationCompletedState.ts | 2 +- .../states/isRightDrawerMinimizedState.ts | 2 +- .../states/isRightDrawerOpenState.ts | 2 +- .../right-drawer/states/messageThreadState.ts | 2 +- .../states/rightDrawerCloseEventsState.ts | 2 +- .../rightDrawerHeaderDropdownButtonState.ts | 2 +- .../states/rightDrawerPageState.ts | 2 +- .../isDefaultLayoutAuthModalVisibleState.ts | 2 +- .../states/navigationMemorizedUrlState.ts | 2 +- .../step-bar/states/stepBarInternalState.ts | 2 +- .../internal/currentHotkeyScopeState.ts | 2 +- .../internalHotkeysEnabledScopesState.ts | 2 +- .../states/internal/pendingHotkeysState.ts | 2 +- .../internal/previousHotkeyScopeState.ts | 2 +- .../states/currentPageLocationState.ts | 2 +- ...rideWorkflowDraftConfirmationModalState.ts | 2 +- ...workflowCreateStepFromParentStepIdState.ts | 2 +- .../workflow/states/workflowDiagramState.ts | 2 +- ...orkflowDiagramTriggerNodeSelectionState.ts | 2 +- .../workflow/states/workflowIdState.ts | 2 +- .../states/workflowLastCreatedStepIdState.ts | 2 +- .../states/workflowReactFlowRefState.ts | 2 +- .../states/workflowSelectedNodeState.ts | 2 +- .../workflow/states/workflowVersionIdState.ts | 2 +- .../states/workspaceInvitationsStates.ts | 2 +- .../states/workspaceAuthProvidersState.ts | 2 +- .../src/pages/onboarding/ChooseYourPlan.tsx | 78 ++++++++++------ .../states/updatedObjectSlugState.ts | 2 +- .../src/testing/decorators/PageDecorator.tsx | 2 +- .../controllers/google-auth.controller.ts | 8 +- .../controllers/microsoft-auth.controller.ts | 5 +- .../auth/guards/google-oauth.guard.ts | 8 ++ .../auth/guards/microsoft-oauth.guard.ts | 8 ++ .../auth/services/auth.service.ts | 14 ++- .../auth/services/sign-in-up.service.ts | 2 +- .../auth/strategies/google.auth.strategy.ts | 8 ++ .../strategies/microsoft.auth.strategy.ts | 8 ++ .../core-modules/billing/billing.resolver.ts | 10 ++- .../billing/dto/checkout-session.input.ts | 24 ++++- .../billing/enums/billing-plan-key.enum.ts | 12 ++- .../billing-portal.workspace-service.ts | 5 ++ .../billing-webhook-product.service.ts | 4 +- .../billing/stripe/stripe.service.ts | 11 ++- yarn.lock | 27 ++++++ 123 files changed, 524 insertions(+), 173 deletions(-) create mode 100644 packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts create mode 100644 packages/twenty-front/src/modules/auth/types/billingCheckoutSession.type.ts diff --git a/README.md b/README.md index 191012dc4..258f6f410 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,13 @@ We felt the need for a CRM platform that empowers rather than constrains. We bel
# Demo + + Go to demo.twenty.com and login with the following credentials: ``` diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 76ae9c72f..3e91a2eea 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -48,6 +48,7 @@ "buffer": "^6.0.3", "docx": "^9.1.0", "file-saver": "^2.0.5", + "recoil-sync": "^0.2.0", "transliteration": "^2.3.5" }, "devDependencies": { diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 6290fbfcd..d06c6140b 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -115,6 +115,13 @@ export type Billing = { isBillingEnabled: Scalars['Boolean']; }; +/** The different billing plans available */ +export enum BillingPlanKey { + Base = 'BASE', + Enterprise = 'ENTERPRISE', + Pro = 'PRO' +} + export type BillingSubscription = { __typename?: 'BillingSubscription'; id: Scalars['UUID']; @@ -182,6 +189,30 @@ export type ComputeStepOutputSchemaInput = { step: Scalars['JSON']; }; +export type CreateFieldInput = { + defaultValue?: InputMaybe; + description?: InputMaybe; + icon?: InputMaybe; + isActive?: InputMaybe; + isCustom?: InputMaybe; + isLabelSyncedWithName?: InputMaybe; + isNullable?: InputMaybe; + isRemoteCreation?: InputMaybe; + isSystem?: InputMaybe; + isUnique?: InputMaybe; + label: Scalars['String']; + name: Scalars['String']; + objectMetadataId: Scalars['String']; + options?: InputMaybe; + settings?: InputMaybe; + type: FieldMetadataType; +}; + +export type CreateOneFieldMetadataInput = { + /** The record to create */ + field: CreateFieldInput; +}; + export type CreateServerlessFunctionInput = { description?: InputMaybe; name: Scalars['String']; @@ -205,6 +236,11 @@ export type CursorPaging = { last?: InputMaybe; }; +export type DeleteOneFieldInput = { + /** The id of the field to delete. */ + id: Scalars['UUID']; +}; + export type DeleteOneObjectInput = { /** The id of the record to delete. */ id: Scalars['UUID']; @@ -447,12 +483,14 @@ export type Mutation = { computeStepOutputSchema: Scalars['JSON']; createOIDCIdentityProvider: SetupSsoOutput; createOneAppToken: AppToken; + createOneField: Field; createOneObject: Object; createOneServerlessFunction: ServerlessFunction; createSAMLIdentityProvider: SetupSsoOutput; createWorkflowVersionStep: WorkflowAction; deactivateWorkflowVersion: Scalars['Boolean']; deleteCurrentWorkspace: Workspace; + deleteOneField: Field; deleteOneObject: Object; deleteOneServerlessFunction: ServerlessFunction; deleteSSOIdentityProvider: DeleteSsoOutput; @@ -478,6 +516,7 @@ export type Mutation = { switchWorkspace: PublicWorkspaceDataOutput; track: Analytics; updateBillingSubscription: UpdateBillingEntity; + updateOneField: Field; updateOneObject: Object; updateOneServerlessFunction: ServerlessFunction; updatePasswordViaResetToken: InvalidatePassword; @@ -528,7 +567,9 @@ export type MutationChallengeArgs = { export type MutationCheckoutSessionArgs = { + plan?: BillingPlanKey; recurringInterval: SubscriptionInterval; + requirePaymentMethod?: Scalars['Boolean']; successUrlPath?: InputMaybe; }; @@ -543,6 +584,11 @@ export type MutationCreateOidcIdentityProviderArgs = { }; +export type MutationCreateOneFieldArgs = { + input: CreateOneFieldMetadataInput; +}; + + export type MutationCreateOneServerlessFunctionArgs = { input: CreateServerlessFunctionInput; }; @@ -563,6 +609,11 @@ export type MutationDeactivateWorkflowVersionArgs = { }; +export type MutationDeleteOneFieldArgs = { + input: DeleteOneFieldInput; +}; + + export type MutationDeleteOneObjectArgs = { input: DeleteOneObjectInput; }; @@ -665,6 +716,11 @@ export type MutationTrackArgs = { }; +export type MutationUpdateOneFieldArgs = { + input: UpdateOneFieldMetadataInput; +}; + + export type MutationUpdateOneObjectArgs = { input: UpdateOneObjectInput; }; @@ -825,6 +881,8 @@ export type Query = { clientConfig: ClientConfig; currentUser: User; currentWorkspace: Workspace; + field: Field; + fields: FieldConnection; findAvailableWorkspacesByEmail: Array; findManyServerlessFunctions: Array; findOneServerlessFunction: ServerlessFunction; @@ -1241,6 +1299,22 @@ export type UpdateBillingEntity = { success: Scalars['Boolean']; }; +export type UpdateFieldInput = { + defaultValue?: InputMaybe; + description?: InputMaybe; + icon?: InputMaybe; + isActive?: InputMaybe; + isCustom?: InputMaybe; + isLabelSyncedWithName?: InputMaybe; + isNullable?: InputMaybe; + isSystem?: InputMaybe; + isUnique?: InputMaybe; + label?: InputMaybe; + name?: InputMaybe; + options?: InputMaybe; + settings?: InputMaybe; +}; + export type UpdateObjectPayload = { description?: InputMaybe; icon?: InputMaybe; @@ -1255,6 +1329,13 @@ export type UpdateObjectPayload = { shortcut?: InputMaybe; }; +export type UpdateOneFieldMetadataInput = { + /** The id of the record to update */ + id: Scalars['UUID']; + /** The record to update */ + update: UpdateFieldInput; +}; + export type UpdateOneObjectInput = { /** The id of the object to update */ id: Scalars['UUID']; @@ -1944,6 +2025,8 @@ export type BillingPortalSessionQuery = { __typename?: 'Query', billingPortalSes export type CheckoutSessionMutationVariables = Exact<{ recurringInterval: SubscriptionInterval; successUrlPath?: InputMaybe; + plan: BillingPlanKey; + requirePaymentMethod: Scalars['Boolean']; }>; @@ -3234,10 +3317,12 @@ export type BillingPortalSessionQueryHookResult = ReturnType; export type BillingPortalSessionQueryResult = Apollo.QueryResult; export const CheckoutSessionDocument = gql` - mutation CheckoutSession($recurringInterval: SubscriptionInterval!, $successUrlPath: String) { + mutation CheckoutSession($recurringInterval: SubscriptionInterval!, $successUrlPath: String, $plan: BillingPlanKey!, $requirePaymentMethod: Boolean!) { checkoutSession( recurringInterval: $recurringInterval successUrlPath: $successUrlPath + plan: $plan + requirePaymentMethod: $requirePaymentMethod ) { url } @@ -3260,6 +3345,8 @@ export type CheckoutSessionMutationFn = Apollo.MutationFunction({ key: 'canCreateActivityState', diff --git a/packages/twenty-front/src/modules/activities/states/isActivityInCreateModeState.ts b/packages/twenty-front/src/modules/activities/states/isActivityInCreateModeState.ts index 61aaf33d3..4ae973044 100644 --- a/packages/twenty-front/src/modules/activities/states/isActivityInCreateModeState.ts +++ b/packages/twenty-front/src/modules/activities/states/isActivityInCreateModeState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isActivityInCreateModeState = createState({ key: 'isActivityInCreateModeState', diff --git a/packages/twenty-front/src/modules/activities/states/isCreatingActivityInDBState.ts b/packages/twenty-front/src/modules/activities/states/isCreatingActivityInDBState.ts index 278b2430c..30e111a66 100644 --- a/packages/twenty-front/src/modules/activities/states/isCreatingActivityInDBState.ts +++ b/packages/twenty-front/src/modules/activities/states/isCreatingActivityInDBState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isUpsertingActivityInDBState = createState({ key: 'isUpsertingActivityInDBState', diff --git a/packages/twenty-front/src/modules/app/components/App.tsx b/packages/twenty-front/src/modules/app/components/App.tsx index f760ee9f6..af4c1eec0 100644 --- a/packages/twenty-front/src/modules/app/components/App.tsx +++ b/packages/twenty-front/src/modules/app/components/App.tsx @@ -7,26 +7,29 @@ import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHa import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { HelmetProvider } from 'react-helmet-async'; import { RecoilRoot } from 'recoil'; +import { RecoilURLSyncJSON } from 'recoil-sync'; import { IconsProvider } from 'twenty-ui'; export const App = () => { return ( - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 659cab893..d28d49d59 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -43,16 +43,17 @@ import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTi import { currentUserState } from '../states/currentUserState'; import { tokenPairState } from '../states/tokenPairState'; +import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain'; import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain'; import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation'; +import { useRedirect } from '@/domain-manager/hooks/useRedirect'; +import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState'; import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState'; -import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; import { AppPath } from '@/types/AppPath'; -import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; -import { useRedirect } from '@/domain-manager/hooks/useRedirect'; +import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; export const useAuth = () => { const setTokenPair = useSetRecoilState(tokenPairState); @@ -382,6 +383,7 @@ export const useAuth = () => { params: { workspacePersonalInviteToken?: string; workspaceInviteHash?: string; + billingCheckoutSession?: BillingCheckoutSession; }, ) => { const url = new URL(`${REACT_APP_SERVER_BASE_URL}${path}`); @@ -394,6 +396,12 @@ export const useAuth = () => { params.workspacePersonalInviteToken, ); } + if (isDefined(params.billingCheckoutSession)) { + url.searchParams.set( + 'billingCheckoutSessionState', + JSON.stringify(params.billingCheckoutSession), + ); + } if (isDefined(workspaceSubdomain)) { url.searchParams.set('workspaceSubdomain', workspaceSubdomain); @@ -408,6 +416,7 @@ export const useAuth = () => { (params: { workspacePersonalInviteToken?: string; workspaceInviteHash?: string; + billingCheckoutSession?: BillingCheckoutSession; }) => { redirect(buildRedirectUrl('/auth/google', params)); }, @@ -418,6 +427,7 @@ export const useAuth = () => { (params: { workspacePersonalInviteToken?: string; workspaceInviteHash?: string; + billingCheckoutSession?: BillingCheckoutSession; }) => { redirect(buildRedirectUrl('/auth/microsoft', params)); }, diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithGoogle.test.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithGoogle.test.ts index 6cb9f84ed..5a7072cea 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithGoogle.test.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithGoogle.test.ts @@ -1,7 +1,9 @@ -import { renderHook } from '@testing-library/react'; -import { useParams, useSearchParams } from 'react-router-dom'; import { useAuth } from '@/auth/hooks/useAuth'; import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle'; +import { renderHook } from '@testing-library/react'; +import { useParams, useSearchParams } from 'react-router-dom'; +import { BillingPlanKey, SubscriptionInterval } from '~/generated/graphql'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; jest.mock('react-router-dom', () => ({ useParams: jest.fn(), @@ -13,10 +15,24 @@ jest.mock('@/auth/hooks/useAuth', () => ({ })); describe('useSignInWithGoogle', () => { + const mockBillingCheckoutSession = { + plan: BillingPlanKey.Pro, + interval: SubscriptionInterval.Month, + requirePaymentMethod: true, + skipPlanPage: false, + }; + + const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], + }); + it('should call signInWithGoogle with correct params', () => { const signInWithGoogleMock = jest.fn(); const mockUseParams = { workspaceInviteHash: 'testHash' }; - const mockSearchParams = new URLSearchParams('inviteToken=testToken'); + + const mockSearchParams = new URLSearchParams( + 'inviteToken=testToken&billingCheckoutSessionState={"plan":"Pro","interval":"Month","requirePaymentMethod":true,"skipPlanPage":false}', + ); (useParams as jest.Mock).mockReturnValue(mockUseParams); (useSearchParams as jest.Mock).mockReturnValue([mockSearchParams]); @@ -24,12 +40,15 @@ describe('useSignInWithGoogle', () => { signInWithGoogle: signInWithGoogleMock, }); - const { result } = renderHook(() => useSignInWithGoogle()); + const { result } = renderHook(() => useSignInWithGoogle(), { + wrapper: Wrapper, + }); result.current.signInWithGoogle(); expect(signInWithGoogleMock).toHaveBeenCalledWith({ workspaceInviteHash: 'testHash', workspacePersonalInviteToken: 'testToken', + billingCheckoutSession: mockBillingCheckoutSession, }); }); @@ -44,12 +63,15 @@ describe('useSignInWithGoogle', () => { signInWithGoogle: signInWithGoogleMock, }); - const { result } = renderHook(() => useSignInWithGoogle()); + const { result } = renderHook(() => useSignInWithGoogle(), { + wrapper: Wrapper, + }); result.current.signInWithGoogle(); expect(signInWithGoogleMock).toHaveBeenCalledWith({ workspaceInviteHash: 'testHash', workspacePersonalInviteToken: undefined, + billingCheckoutSession: mockBillingCheckoutSession, }); }); }); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithMicrosoft.test.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithMicrosoft.test.ts index 3200e8a54..98c9ea527 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithMicrosoft.test.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInWithMicrosoft.test.ts @@ -1,7 +1,8 @@ -import { renderHook } from '@testing-library/react'; -import { useParams, useSearchParams } from 'react-router-dom'; import { useAuth } from '@/auth/hooks/useAuth'; import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft'; +import { renderHook } from '@testing-library/react'; +import { useParams, useSearchParams } from 'react-router-dom'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; jest.mock('react-router-dom', () => ({ useParams: jest.fn(), @@ -13,6 +14,17 @@ jest.mock('@/auth/hooks/useAuth', () => ({ })); describe('useSignInWithMicrosoft', () => { + const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], + }); + + const mockBillingCheckoutSession = { + plan: 'PRO', + interval: 'Month', + requirePaymentMethod: true, + skipPlanPage: false, + }; + it('should call signInWithMicrosoft with the correct parameters', () => { const workspaceInviteHashMock = 'testHash'; const inviteTokenMock = 'testToken'; @@ -28,12 +40,15 @@ describe('useSignInWithMicrosoft', () => { signInWithMicrosoft: signInWithMicrosoftMock, }); - const { result } = renderHook(() => useSignInWithMicrosoft()); + const { result } = renderHook(() => useSignInWithMicrosoft(), { + wrapper: Wrapper, + }); result.current.signInWithMicrosoft(); expect(signInWithMicrosoftMock).toHaveBeenCalledWith({ workspaceInviteHash: workspaceInviteHashMock, workspacePersonalInviteToken: inviteTokenMock, + billingCheckoutSession: mockBillingCheckoutSession, }); }); @@ -49,10 +64,13 @@ describe('useSignInWithMicrosoft', () => { signInWithMicrosoft: signInWithMicrosoftMock, }); - const { result } = renderHook(() => useSignInWithMicrosoft()); + const { result } = renderHook(() => useSignInWithMicrosoft(), { + wrapper: Wrapper, + }); result.current.signInWithMicrosoft(); expect(signInWithMicrosoftMock).toHaveBeenCalledWith({ + billingCheckoutSession: mockBillingCheckoutSession, workspaceInviteHash: workspaceInviteHashMock, workspacePersonalInviteToken: undefined, }); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithGoogle.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithGoogle.ts index 58ce165f7..26908402e 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithGoogle.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithGoogle.ts @@ -1,15 +1,27 @@ import { useParams, useSearchParams } from 'react-router-dom'; import { useAuth } from '@/auth/hooks/useAuth'; +import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; export const useSignInWithGoogle = () => { const workspaceInviteHash = useParams().workspaceInviteHash; const [searchParams] = useSearchParams(); const workspacePersonalInviteToken = searchParams.get('inviteToken') ?? undefined; + const billingCheckoutSession = { + plan: 'PRO', + interval: 'Month', + requirePaymentMethod: true, + skipPlanPage: false, + } as BillingCheckoutSession; + const { signInWithGoogle } = useAuth(); return { signInWithGoogle: () => - signInWithGoogle({ workspaceInviteHash, workspacePersonalInviteToken }), + signInWithGoogle({ + workspaceInviteHash, + workspacePersonalInviteToken, + billingCheckoutSession, + }), }; }; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts index 2f471c176..513f77ac6 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts @@ -1,18 +1,23 @@ import { useParams, useSearchParams } from 'react-router-dom'; import { useAuth } from '@/auth/hooks/useAuth'; +import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState'; +import { useRecoilValue } from 'recoil'; export const useSignInWithMicrosoft = () => { const workspaceInviteHash = useParams().workspaceInviteHash; const [searchParams] = useSearchParams(); const workspacePersonalInviteToken = searchParams.get('inviteToken') ?? undefined; + const billingCheckoutSession = useRecoilValue(billingCheckoutSessionState); + const { signInWithMicrosoft } = useAuth(); return { signInWithMicrosoft: () => signInWithMicrosoft({ workspaceInviteHash, workspacePersonalInviteToken, + billingCheckoutSession, }), }; }; diff --git a/packages/twenty-front/src/modules/auth/states/availableIdentityProviderForAuthState.ts b/packages/twenty-front/src/modules/auth/states/availableIdentityProviderForAuthState.ts index d3100a826..a8ecdfd93 100644 --- a/packages/twenty-front/src/modules/auth/states/availableIdentityProviderForAuthState.ts +++ b/packages/twenty-front/src/modules/auth/states/availableIdentityProviderForAuthState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { UserExists } from '~/generated/graphql'; export const availableSSOIdentityProvidersForAuthState = createState< diff --git a/packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts b/packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts new file mode 100644 index 000000000..acfe231a9 --- /dev/null +++ b/packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts @@ -0,0 +1,39 @@ +import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; +import { createState } from '@ui/utilities/state/utils/createState'; +import { syncEffect } from 'recoil-sync'; +import { BillingPlanKey, SubscriptionInterval } from '~/generated/graphql'; + +export const billingCheckoutSessionState = createState({ + key: 'billingCheckoutSessionState', + defaultValue: { + plan: BillingPlanKey.Pro, + interval: SubscriptionInterval.Month, + requirePaymentMethod: true, + skipPlanPage: false, + }, + effects: [ + syncEffect({ + refine: (value: unknown) => { + if ( + typeof value === 'object' && + value !== null && + 'plan' in value && + 'interval' in value && + 'requirePaymentMethod' in value && + 'skipPlanPage' in value + ) { + return { + type: 'success', + value: value as BillingCheckoutSession, + warnings: [], + } as const; + } + return { + type: 'failure', + message: 'Invalid BillingCheckoutSessionState', + path: [] as any, + } as const; + }, + }), + ], +}); diff --git a/packages/twenty-front/src/modules/auth/states/currentUserState.ts b/packages/twenty-front/src/modules/auth/states/currentUserState.ts index 26880ddae..72f11157a 100644 --- a/packages/twenty-front/src/modules/auth/states/currentUserState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentUserState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { User } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts index 4af6c5b8f..33013f124 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersStates.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersStates.ts index a8059259a..160e786b6 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersStates.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersStates.ts @@ -1,5 +1,5 @@ import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const currentWorkspaceMembersState = createState< CurrentWorkspaceMember[] diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts index c06276c12..872c6af93 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Workspace } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts b/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts index 0a62d92ab..242739b7b 100644 --- a/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts +++ b/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadingState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isCurrentUserLoadedState = createState({ key: 'isCurrentUserLoadedState', diff --git a/packages/twenty-front/src/modules/auth/states/isVerifyPendingState.ts b/packages/twenty-front/src/modules/auth/states/isVerifyPendingState.ts index 567910389..0e554b0c8 100644 --- a/packages/twenty-front/src/modules/auth/states/isVerifyPendingState.ts +++ b/packages/twenty-front/src/modules/auth/states/isVerifyPendingState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isVerifyPendingState = createState({ key: 'isVerifyPendingState', diff --git a/packages/twenty-front/src/modules/auth/states/previousUrlState.ts b/packages/twenty-front/src/modules/auth/states/previousUrlState.ts index 1de274fb1..951436d33 100644 --- a/packages/twenty-front/src/modules/auth/states/previousUrlState.ts +++ b/packages/twenty-front/src/modules/auth/states/previousUrlState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const previousUrlState = createState({ key: 'previousUrlState', diff --git a/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts b/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts index 98da3b608..398fe2604 100644 --- a/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts +++ b/packages/twenty-front/src/modules/auth/states/signInUpModeState.ts @@ -1,5 +1,5 @@ -import { createState } from 'twenty-ui'; import { SignInUpMode } from '@/auth/types/signInUpMode'; +import { createState } from '@ui/utilities/state/utils/createState'; export const signInUpModeState = createState({ key: 'signInUpModeState', diff --git a/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts b/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts index b3f3c4081..81a402371 100644 --- a/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts +++ b/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export enum SignInUpStep { Init = 'init', diff --git a/packages/twenty-front/src/modules/auth/states/tokenPairState.ts b/packages/twenty-front/src/modules/auth/states/tokenPairState.ts index 718e67ad6..2d8d597e4 100644 --- a/packages/twenty-front/src/modules/auth/states/tokenPairState.ts +++ b/packages/twenty-front/src/modules/auth/states/tokenPairState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { AuthTokenPair } from '~/generated/graphql'; import { cookieStorageEffect } from '~/utils/recoil-effects'; diff --git a/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts b/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts index f4866fa65..64bafed36 100644 --- a/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts +++ b/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { PublicWorkspaceDataOutput } from '~/generated/graphql'; export const workspacePublicDataState = diff --git a/packages/twenty-front/src/modules/auth/states/workspaces.ts b/packages/twenty-front/src/modules/auth/states/workspaces.ts index da0d270bb..e38a4a146 100644 --- a/packages/twenty-front/src/modules/auth/states/workspaces.ts +++ b/packages/twenty-front/src/modules/auth/states/workspaces.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Workspace } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/auth/types/billingCheckoutSession.type.ts b/packages/twenty-front/src/modules/auth/types/billingCheckoutSession.type.ts new file mode 100644 index 000000000..a634e17e2 --- /dev/null +++ b/packages/twenty-front/src/modules/auth/types/billingCheckoutSession.type.ts @@ -0,0 +1,9 @@ +import { SubscriptionInterval } from '~/generated-metadata/graphql'; +import { BillingPlanKey } from '~/generated/graphql'; + +export type BillingCheckoutSession = { + plan: BillingPlanKey; + interval: SubscriptionInterval; + requirePaymentMethod: boolean; + skipPlanPage: boolean; +}; diff --git a/packages/twenty-front/src/modules/billing/graphql/checkoutSession.ts b/packages/twenty-front/src/modules/billing/graphql/checkoutSession.ts index 53821543c..91820e9f0 100644 --- a/packages/twenty-front/src/modules/billing/graphql/checkoutSession.ts +++ b/packages/twenty-front/src/modules/billing/graphql/checkoutSession.ts @@ -4,10 +4,14 @@ export const CHECKOUT_SESSION = gql` mutation CheckoutSession( $recurringInterval: SubscriptionInterval! $successUrlPath: String + $plan: BillingPlanKey! + $requirePaymentMethod: Boolean! ) { checkoutSession( recurringInterval: $recurringInterval successUrlPath: $successUrlPath + plan: $plan + requirePaymentMethod: $requirePaymentMethod ) { url } diff --git a/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts b/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts index 24289cac6..7ec02e494 100644 --- a/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts +++ b/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const captchaTokenState = createState({ key: 'captchaTokenState', diff --git a/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts b/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts index 0db486bbe..f6fa69169 100644 --- a/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts +++ b/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isCaptchaScriptLoadedState = createState({ key: 'isCaptchaScriptLoadedState', diff --git a/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts b/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts index df7daeb4d..86f4e9f6a 100644 --- a/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts +++ b/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isRequestingCaptchaTokenState = createState({ key: 'isRequestingCaptchaTokenState', diff --git a/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts b/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts index e04798f19..91525e562 100644 --- a/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts +++ b/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isLoadingTokensFromExtensionState = createState({ key: 'isLoadingTokensFromExtensionState', diff --git a/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts b/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts index 9a01493e4..77e8295c7 100644 --- a/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts +++ b/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { ApiConfig } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/client-config/states/authProvidersState.ts b/packages/twenty-front/src/modules/client-config/states/authProvidersState.ts index d56572b5a..46646c40d 100644 --- a/packages/twenty-front/src/modules/client-config/states/authProvidersState.ts +++ b/packages/twenty-front/src/modules/client-config/states/authProvidersState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { AuthProviders } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/client-config/states/billingState.ts b/packages/twenty-front/src/modules/client-config/states/billingState.ts index 5634c510b..b188943b0 100644 --- a/packages/twenty-front/src/modules/client-config/states/billingState.ts +++ b/packages/twenty-front/src/modules/client-config/states/billingState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Billing } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/client-config/states/captchaProviderState.ts b/packages/twenty-front/src/modules/client-config/states/captchaProviderState.ts index 2440f11a4..ca312acf0 100644 --- a/packages/twenty-front/src/modules/client-config/states/captchaProviderState.ts +++ b/packages/twenty-front/src/modules/client-config/states/captchaProviderState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Captcha } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/client-config/states/chromeExtensionIdState.ts b/packages/twenty-front/src/modules/client-config/states/chromeExtensionIdState.ts index bec5ae986..b29db3633 100644 --- a/packages/twenty-front/src/modules/client-config/states/chromeExtensionIdState.ts +++ b/packages/twenty-front/src/modules/client-config/states/chromeExtensionIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const chromeExtensionIdState = createState({ key: 'chromeExtensionIdState', diff --git a/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts index 08793188f..9f5f6f9e5 100644 --- a/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts +++ b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; type ClientConfigApiStatus = { isLoaded: boolean; diff --git a/packages/twenty-front/src/modules/client-config/states/isAnalyticsEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isAnalyticsEnabledState.ts index 50c0f5c89..f21835209 100644 --- a/packages/twenty-front/src/modules/client-config/states/isAnalyticsEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isAnalyticsEnabledState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isAnalyticsEnabledState = createState({ key: 'isAnalyticsEnabled', diff --git a/packages/twenty-front/src/modules/client-config/states/isDebugModeState.ts b/packages/twenty-front/src/modules/client-config/states/isDebugModeState.ts index b2efbf8fc..037661f1a 100644 --- a/packages/twenty-front/src/modules/client-config/states/isDebugModeState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isDebugModeState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isDebugModeState = createState({ key: 'isDebugModeState', diff --git a/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts b/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts index 5f4d82bed..30839339a 100644 --- a/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isDeveloperDefaultSignInPrefilledState = createState({ key: 'isDeveloperDefaultSignInPrefilledState', diff --git a/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts index 4749eeab9..a18c415f5 100644 --- a/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isMultiWorkspaceEnabledState = createState({ key: 'isMultiWorkspaceEnabled', diff --git a/packages/twenty-front/src/modules/client-config/states/isSSOEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isSSOEnabledState.ts index 3242678c3..7d40b6733 100644 --- a/packages/twenty-front/src/modules/client-config/states/isSSOEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isSSOEnabledState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isSSOEnabledState = createState({ key: 'isSSOEnabledState', diff --git a/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts b/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts index 19ca03599..5046ce9b6 100644 --- a/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts +++ b/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Sentry } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/client-config/states/supportChatState.ts b/packages/twenty-front/src/modules/client-config/states/supportChatState.ts index cca337ba5..81d7fb901 100644 --- a/packages/twenty-front/src/modules/client-config/states/supportChatState.ts +++ b/packages/twenty-front/src/modules/client-config/states/supportChatState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Support } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/command-menu/states/commandMenuSearchState.ts b/packages/twenty-front/src/modules/command-menu/states/commandMenuSearchState.ts index 61f580a8a..b7315d5d0 100644 --- a/packages/twenty-front/src/modules/command-menu/states/commandMenuSearchState.ts +++ b/packages/twenty-front/src/modules/command-menu/states/commandMenuSearchState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const commandMenuSearchState = createState({ key: 'command-menu/commandMenuSearchState', diff --git a/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts b/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts index 461c03be8..2a1fffbb2 100644 --- a/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts +++ b/packages/twenty-front/src/modules/context-store/states/mainContextStoreComponentInstanceId.ts @@ -1,5 +1,5 @@ import { CONTEXT_STORE_INSTANCE_ID_DEFAULT_VALUE } from '@/context-store/constants/ContextStoreInstanceIdDefaultValue'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const mainContextStoreComponentInstanceIdState = createState({ key: 'mainContextStoreComponentInstanceIdState', diff --git a/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts b/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts index 89dc12404..0ea281bce 100644 --- a/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts +++ b/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { ClientConfig } from '~/generated/graphql'; export const domainConfigurationState = createState< diff --git a/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts b/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts index 6d8e458a3..67c44f43f 100644 --- a/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts +++ b/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts @@ -1,5 +1,5 @@ +import { createState } from '@ui/utilities/state/utils/createState'; import { cookieStorageEffect } from '~/utils/recoil-effects'; -import { createState } from 'twenty-ui'; export const lastAuthenticatedWorkspaceDomainState = createState< | { diff --git a/packages/twenty-front/src/modules/localization/states/dateTimeFormatState.ts b/packages/twenty-front/src/modules/localization/states/dateTimeFormatState.ts index 2392151e3..60bd66c61 100644 --- a/packages/twenty-front/src/modules/localization/states/dateTimeFormatState.ts +++ b/packages/twenty-front/src/modules/localization/states/dateTimeFormatState.ts @@ -1,7 +1,7 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; import { detectTimeZone } from '@/localization/utils/detectTimeZone'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const dateTimeFormatState = createState<{ timeZone: string; diff --git a/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts b/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts index c376b1953..540c6fad1 100644 --- a/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts +++ b/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const currentMobileNavigationDrawerState = createState< 'main' | 'settings' diff --git a/packages/twenty-front/src/modules/object-metadata/states/isAppWaitingForFreshObjectMetadataState.ts b/packages/twenty-front/src/modules/object-metadata/states/isAppWaitingForFreshObjectMetadataState.ts index 2cbe59765..e5eb25380 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/isAppWaitingForFreshObjectMetadataState.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/isAppWaitingForFreshObjectMetadataState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isAppWaitingForFreshObjectMetadataState = createState({ key: 'isAppWaitingForFreshObjectMetadataState', diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts index 5c247fe0e..57545e5b1 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/states/aggregateDropdownState.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/states/aggregateDropdownState.ts index 31eeb84fc..310f9db0d 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/states/aggregateDropdownState.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/states/aggregateDropdownState.ts @@ -1,5 +1,5 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; type AggregateOperation = { operation: AGGREGATE_OPERATIONS | null; diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/lastShowPageRecordId.ts b/packages/twenty-front/src/modules/object-record/record-field/states/lastShowPageRecordId.ts index dce923b37..42e159bbd 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/states/lastShowPageRecordId.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/states/lastShowPageRecordId.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const lastShowPageRecordIdState = createState({ key: 'lastShowPageRecordIdState', diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFieldDefinitionsState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFieldDefinitionsState.ts index 1b260d3c9..dfd92d42e 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFieldDefinitionsState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFieldDefinitionsState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFiltersState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFiltersState.ts index fc3462109..6af604887 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFiltersState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexFiltersState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexIsCompactModeActiveState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexIsCompactModeActiveState.ts index cd1d83fbb..3cc57f14f 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexIsCompactModeActiveState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexIsCompactModeActiveState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const recordIndexIsCompactModeActiveState = createState({ key: 'recordIndexIsCompactModeActiveState', diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanAggregateOperationState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanAggregateOperationState.ts index 9b60fae0c..664a6755c 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanAggregateOperationState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanAggregateOperationState.ts @@ -1,5 +1,5 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export type KanbanAggregateOperation = { operation?: AGGREGATE_OPERATIONS | null; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState.ts index f1527934d..bb28f4ad0 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const recordIndexKanbanFieldMetadataIdState = createState( { diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts index 57088c8e5..2d839afdd 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexSortsState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { Sort } from '@/object-record/object-sort-dropdown/types/Sort'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewFilterGroupsState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewFilterGroupsState.ts index 978614756..ea07e363d 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewFilterGroupsState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewFilterGroupsState.ts @@ -1,5 +1,5 @@ import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const recordIndexViewFilterGroupsState = createState({ key: 'recordIndexViewFilterGroupsState', diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewTypeState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewTypeState.ts index 49b032c86..ad8fbfc5c 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewTypeState.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexViewTypeState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { ViewType } from '@/views/types/ViewType'; diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts index 904677204..7e5b6e2bc 100644 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts +++ b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isNewViewableRecordLoadingState = createState({ key: 'activities/is-new-viewable-record-loading', diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordIdState.ts b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordIdState.ts index c75e6cf8a..ae72a9435 100644 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordIdState.ts +++ b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const viewableRecordIdState = createState({ key: 'activities/viewable-record-id', diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordNameSingularState.ts b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordNameSingularState.ts index 3116430e1..6e0e5a6ea 100644 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordNameSingularState.ts +++ b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/viewableRecordNameSingularState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const viewableRecordNameSingularState = createState({ key: 'activities/viewable-record-name-singular', diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordInGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordInGroup.test.tsx index ee690ec18..84ab42477 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordInGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordInGroup.test.tsx @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; +import { createState } from '@ui/utilities/state/utils/createState'; import { ReactNode, act } from 'react'; import { RecoilRoot } from 'recoil'; -import { createState } from 'twenty-ui'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordNoGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordNoGroup.test.tsx index d1f29e2c9..ac3e65e89 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordNoGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordNoGroup.test.tsx @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; +import { createState } from '@ui/utilities/state/utils/createState'; import { ReactNode, act } from 'react'; import { RecoilRoot } from 'recoil'; -import { createState } from 'twenty-ui'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts index 9f8627f2a..542c069d2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/states/isRemoveSortingModalOpenState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isRemoveSortingModalOpenState = createState({ key: 'isRemoveSortingModalOpenState', diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/isSoftFocusUsingMouseState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/isSoftFocusUsingMouseState.ts index d25f46faa..4d295db42 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/states/isSoftFocusUsingMouseState.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/states/isSoftFocusUsingMouseState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isSoftFocusUsingMouseState = createState({ key: 'isSoftFocusUsingMouseState', diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/states/settingsPreviewRecordIdState.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/states/settingsPreviewRecordIdState.ts index 68a5bbff9..a810c8c00 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/states/settingsPreviewRecordIdState.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/states/settingsPreviewRecordIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const settingsPreviewRecordIdState = createState({ key: 'settingsPreviewRecordIdState', diff --git a/packages/twenty-front/src/modules/settings/developers/states/generatedApiKeyTokenState.ts b/packages/twenty-front/src/modules/settings/developers/states/generatedApiKeyTokenState.ts index 6129e451c..0fc6a43a4 100644 --- a/packages/twenty-front/src/modules/settings/developers/states/generatedApiKeyTokenState.ts +++ b/packages/twenty-front/src/modules/settings/developers/states/generatedApiKeyTokenState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const apiKeyTokenState = createState({ key: 'apiKeyTokenState', diff --git a/packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProvidersState.ts b/packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProvidersState.ts index 76dc7cfdf..db9cd89fb 100644 --- a/packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProvidersState.ts +++ b/packages/twenty-front/src/modules/settings/security/states/SSOIdentitiesProvidersState.ts @@ -1,7 +1,7 @@ /* @license Enterprise */ import { SSOIdentityProvider } from '@/settings/security/types/SSOIdentityProvider'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const SSOIdentitiesProvidersState = createState< Omit[] diff --git a/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts b/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts index fceda6736..4aad187fa 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { SpreadsheetImportDialogOptions } from '../types'; diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/states/activeDropdownFocusIdState.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/states/activeDropdownFocusIdState.ts index fca8a5ba6..17cf76ba6 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/states/activeDropdownFocusIdState.ts +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/states/activeDropdownFocusIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const activeDropdownFocusIdState = createState({ key: 'activeDropdownFocusIdState', diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/states/previousDropdownFocusIdState.ts b/packages/twenty-front/src/modules/ui/layout/dropdown/states/previousDropdownFocusIdState.ts index e311d1f3f..060d78a19 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/states/previousDropdownFocusIdState.ts +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/states/previousDropdownFocusIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const previousDropdownFocusIdState = createState({ key: 'previousDropdownFocusIdState', diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState.ts index 3e1cb030a..61d75e2d5 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isRightDrawerAnimationCompletedState = createState({ key: 'isRightDrawerAnimationCompletedState', diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerMinimizedState.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerMinimizedState.ts index 9b2124030..822f909c0 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerMinimizedState.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerMinimizedState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isRightDrawerMinimizedState = createState({ key: 'ui/layout/is-right-drawer-minimized', diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerOpenState.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerOpenState.ts index b0149a588..0fad6afab 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerOpenState.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/isRightDrawerOpenState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isRightDrawerOpenState = createState({ key: 'ui/layout/is-right-drawer-open', diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/messageThreadState.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/messageThreadState.ts index 2e1febcf6..77f9cd76a 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/messageThreadState.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/messageThreadState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { MessageThread } from '@/activities/emails/types/MessageThread'; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerCloseEventsState.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerCloseEventsState.ts index ac0813d8e..9601f1572 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerCloseEventsState.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerCloseEventsState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const rightDrawerCloseEventState = createState({ key: 'rightDrawerCloseEventState', diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerHeaderDropdownButtonState.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerHeaderDropdownButtonState.ts index a0f2a8496..e61330290 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerHeaderDropdownButtonState.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerHeaderDropdownButtonState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { RightDrawerTopBarDropdownButtons } from '@/ui/layout/right-drawer/types/RightDrawerTopBarDropdownButtons'; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerPageState.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerPageState.ts index 0cbcdee85..441988db3 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerPageState.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/states/rightDrawerPageState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { RightDrawerPages } from '../types/RightDrawerPages'; diff --git a/packages/twenty-front/src/modules/ui/layout/states/isDefaultLayoutAuthModalVisibleState.ts b/packages/twenty-front/src/modules/ui/layout/states/isDefaultLayoutAuthModalVisibleState.ts index c181a6a7d..7054e811e 100644 --- a/packages/twenty-front/src/modules/ui/layout/states/isDefaultLayoutAuthModalVisibleState.ts +++ b/packages/twenty-front/src/modules/ui/layout/states/isDefaultLayoutAuthModalVisibleState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const isDefaultLayoutAuthModalVisibleState = createState({ key: 'isDefaultLayoutAuthModalVisibleState', diff --git a/packages/twenty-front/src/modules/ui/navigation/states/navigationMemorizedUrlState.ts b/packages/twenty-front/src/modules/ui/navigation/states/navigationMemorizedUrlState.ts index bb8318ecd..b8b681891 100644 --- a/packages/twenty-front/src/modules/ui/navigation/states/navigationMemorizedUrlState.ts +++ b/packages/twenty-front/src/modules/ui/navigation/states/navigationMemorizedUrlState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const navigationMemorizedUrlState = createState({ key: 'navigationMemorizedUrlState', diff --git a/packages/twenty-front/src/modules/ui/navigation/step-bar/states/stepBarInternalState.ts b/packages/twenty-front/src/modules/ui/navigation/step-bar/states/stepBarInternalState.ts index d2bf60d60..e25c33352 100644 --- a/packages/twenty-front/src/modules/ui/navigation/step-bar/states/stepBarInternalState.ts +++ b/packages/twenty-front/src/modules/ui/navigation/step-bar/states/stepBarInternalState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export type StepsState = { activeStep: number; diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/currentHotkeyScopeState.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/currentHotkeyScopeState.ts index 6b41a6705..61b8165f0 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/currentHotkeyScopeState.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/currentHotkeyScopeState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { INITIAL_HOTKEYS_SCOPE } from '../../constants/InitialHotkeysScope'; import { HotkeyScope } from '../../types/HotkeyScope'; diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState.ts index e2e06b62f..9478ec886 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const internalHotkeysEnabledScopesState = createState({ key: 'internalHotkeysEnabledScopesState', diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/pendingHotkeysState.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/pendingHotkeysState.ts index 13e1cc652..d4d8db3b1 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/pendingHotkeysState.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/pendingHotkeysState.ts @@ -1,5 +1,5 @@ +import { createState } from '@ui/utilities/state/utils/createState'; import { Keys } from 'react-hotkeys-hook/dist/types'; -import { createState } from 'twenty-ui'; export const pendingHotkeyState = createState({ key: 'pendingHotkeyState', diff --git a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/previousHotkeyScopeState.ts b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/previousHotkeyScopeState.ts index 3e438fef1..4585d8f32 100644 --- a/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/previousHotkeyScopeState.ts +++ b/packages/twenty-front/src/modules/ui/utilities/hotkey/states/internal/previousHotkeyScopeState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { HotkeyScope } from '../../types/HotkeyScope'; diff --git a/packages/twenty-front/src/modules/ui/utilities/loading-state/states/currentPageLocationState.ts b/packages/twenty-front/src/modules/ui/utilities/loading-state/states/currentPageLocationState.ts index 601831e5a..0deeeb3dd 100644 --- a/packages/twenty-front/src/modules/ui/utilities/loading-state/states/currentPageLocationState.ts +++ b/packages/twenty-front/src/modules/ui/utilities/loading-state/states/currentPageLocationState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const currentPageLocationState = createState({ key: 'currentPageLocationState', diff --git a/packages/twenty-front/src/modules/workflow/states/openOverrideWorkflowDraftConfirmationModalState.ts b/packages/twenty-front/src/modules/workflow/states/openOverrideWorkflowDraftConfirmationModalState.ts index 1320a9642..97da85be3 100644 --- a/packages/twenty-front/src/modules/workflow/states/openOverrideWorkflowDraftConfirmationModalState.ts +++ b/packages/twenty-front/src/modules/workflow/states/openOverrideWorkflowDraftConfirmationModalState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const openOverrideWorkflowDraftConfirmationModalState = createState({ diff --git a/packages/twenty-front/src/modules/workflow/states/workflowCreateStepFromParentStepIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowCreateStepFromParentStepIdState.ts index 4e64010b9..95752ce60 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowCreateStepFromParentStepIdState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowCreateStepFromParentStepIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workflowCreateStepFromParentStepIdState = createState< string | undefined diff --git a/packages/twenty-front/src/modules/workflow/states/workflowDiagramState.ts b/packages/twenty-front/src/modules/workflow/states/workflowDiagramState.ts index 5f51bf85c..2d2f22a7c 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowDiagramState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowDiagramState.ts @@ -1,5 +1,5 @@ import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram'; -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workflowDiagramState = createState({ key: 'workflowDiagramState', diff --git a/packages/twenty-front/src/modules/workflow/states/workflowDiagramTriggerNodeSelectionState.ts b/packages/twenty-front/src/modules/workflow/states/workflowDiagramTriggerNodeSelectionState.ts index 91630a300..06e64aded 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowDiagramTriggerNodeSelectionState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowDiagramTriggerNodeSelectionState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workflowDiagramTriggerNodeSelectionState = createState< string | undefined diff --git a/packages/twenty-front/src/modules/workflow/states/workflowIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowIdState.ts index 112729ea4..f107f5dd4 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowIdState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workflowIdState = createState({ key: 'workflowIdState', diff --git a/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts index e39718e9c..998ac7a86 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowLastCreatedStepIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workflowLastCreatedStepIdState = createState({ key: 'workflowLastCreatedStepIdState', diff --git a/packages/twenty-front/src/modules/workflow/states/workflowReactFlowRefState.ts b/packages/twenty-front/src/modules/workflow/states/workflowReactFlowRefState.ts index 7a56abac0..579eff7db 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowReactFlowRefState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowReactFlowRefState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { RefObject } from 'react'; export const workflowReactFlowRefState = diff --git a/packages/twenty-front/src/modules/workflow/states/workflowSelectedNodeState.ts b/packages/twenty-front/src/modules/workflow/states/workflowSelectedNodeState.ts index d920489d6..b393f8627 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowSelectedNodeState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowSelectedNodeState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workflowSelectedNodeState = createState({ key: 'workflowSelectedNodeState', diff --git a/packages/twenty-front/src/modules/workflow/states/workflowVersionIdState.ts b/packages/twenty-front/src/modules/workflow/states/workflowVersionIdState.ts index 289469796..f72cb3933 100644 --- a/packages/twenty-front/src/modules/workflow/states/workflowVersionIdState.ts +++ b/packages/twenty-front/src/modules/workflow/states/workflowVersionIdState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workflowVersionIdState = createState({ key: 'workflowVersionIdState', diff --git a/packages/twenty-front/src/modules/workspace-invitation/states/workspaceInvitationsStates.ts b/packages/twenty-front/src/modules/workspace-invitation/states/workspaceInvitationsStates.ts index 8f550a1ad..80b84c3ab 100644 --- a/packages/twenty-front/src/modules/workspace-invitation/states/workspaceInvitationsStates.ts +++ b/packages/twenty-front/src/modules/workspace-invitation/states/workspaceInvitationsStates.ts @@ -1,5 +1,5 @@ -import { createState } from 'twenty-ui'; import { WorkspaceInvitation } from '@/workspace-member/types/WorkspaceMember'; +import { createState } from '@ui/utilities/state/utils/createState'; export const workspaceInvitationsState = createState< Omit[] diff --git a/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts b/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts index 43b360a0c..69384bbb5 100644 --- a/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts +++ b/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; import { AuthProviders } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx index 15f8a4d79..493a8dfae 100644 --- a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx +++ b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx @@ -1,6 +1,7 @@ import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; import { useAuth } from '@/auth/hooks/useAuth'; +import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState'; import { SubscriptionBenefit } from '@/billing/components/SubscriptionBenefit'; import { SubscriptionCard } from '@/billing/components/SubscriptionCard'; import { billingState } from '@/client-config/states/billingState'; @@ -10,7 +11,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import styled from '@emotion/styled'; import { isNonEmptyString, isNumber } from '@sniptt/guards'; import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { ActionLink, CAL_LINK, @@ -77,8 +78,6 @@ const benefits = [ export const ChooseYourPlan = () => { const billing = useRecoilValue(billingState); - const [planSelected, setPlanSelected] = useState(SubscriptionInterval.Month); - const [isSubmitting, setIsSubmitting] = useState(false); const { enqueueSnackBar } = useSnackBar(); @@ -87,12 +86,44 @@ export const ChooseYourPlan = () => { variables: { product: 'base-plan' }, }); + const [billingCheckoutSession, setBillingCheckoutSession] = useRecoilState( + billingCheckoutSessionState, + ); + const [checkoutSession] = useCheckoutSessionMutation(); - const handlePlanChange = (type?: SubscriptionInterval) => { + const handleCheckoutSession = async () => { + setIsSubmitting(true); + const { data } = await checkoutSession({ + variables: { + recurringInterval: billingCheckoutSession.interval, + successUrlPath: AppPath.PlanRequiredSuccess, + plan: billingCheckoutSession.plan, + requirePaymentMethod: billingCheckoutSession.requirePaymentMethod, + }, + }); + setIsSubmitting(false); + if (!data?.checkoutSession.url) { + enqueueSnackBar( + 'Checkout session error. Please retry or contact Twenty team', + { + variant: SnackBarVariant.Error, + }, + ); + return; + } + window.location.replace(data.checkoutSession.url); + }; + + const handleIntervalChange = (type?: SubscriptionInterval) => { return () => { - if (isNonEmptyString(type) && planSelected !== type) { - setPlanSelected(type); + if (isNonEmptyString(type) && billingCheckoutSession.interval !== type) { + setBillingCheckoutSession({ + plan: billingCheckoutSession.plan, + interval: type, + requirePaymentMethod: billingCheckoutSession.requirePaymentMethod, + skipPlanPage: false, + }); } }; }; @@ -121,26 +152,13 @@ export const ChooseYourPlan = () => { return 'Cancel anytime'; }; - const handleButtonClick = async () => { - setIsSubmitting(true); - const { data } = await checkoutSession({ - variables: { - recurringInterval: planSelected, - successUrlPath: AppPath.PlanRequiredSuccess, - }, - }); - setIsSubmitting(false); - if (!data?.checkoutSession.url) { - enqueueSnackBar( - 'Checkout session error. Please retry or contact Twenty team', - { - variant: SnackBarVariant.Error, - }, - ); - return; - } - window.location.replace(data.checkoutSession.url); - }; + if (billingCheckoutSession.skipPlanPage && !isSubmitting) { + handleCheckoutSession(); + } + + if (billingCheckoutSession.skipPlanPage || isSubmitting) { + return ; + } return ( prices?.getProductPrices?.productPrices && ( @@ -152,8 +170,10 @@ export const ChooseYourPlan = () => { {prices.getProductPrices.productPrices.map((price, index) => ( { isSubmitting && } disabled={isSubmitting} diff --git a/packages/twenty-front/src/pages/settings/data-model/states/updatedObjectSlugState.ts b/packages/twenty-front/src/pages/settings/data-model/states/updatedObjectSlugState.ts index be64d0b50..6140293dc 100644 --- a/packages/twenty-front/src/pages/settings/data-model/states/updatedObjectSlugState.ts +++ b/packages/twenty-front/src/pages/settings/data-model/states/updatedObjectSlugState.ts @@ -1,4 +1,4 @@ -import { createState } from 'twenty-ui'; +import { createState } from '@ui/utilities/state/utils/createState'; export const updatedObjectSlugState = createState({ key: 'updatedObjectSlugState', diff --git a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx index 97232ffc1..1fcbce56f 100644 --- a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx @@ -23,9 +23,9 @@ import { mockedApolloClient } from '~/testing/mockedApolloClient'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; +import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect'; import { IconsProvider } from 'twenty-ui'; import { FullHeightStorybookLayout } from '../FullHeightStorybookLayout'; -import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect'; export type PageDecoratorArgs = { routePath: string; diff --git a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts index bb5414f08..31b83489f 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-auth.controller.ts @@ -8,9 +8,10 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import { Response } from 'express'; +import { Repository } from 'typeorm'; +import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; import { AuthOAuthExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-oauth-exception.filter'; import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter'; import { GoogleOauthGuard } from 'src/engine/core-modules/auth/guards/google-oauth.guard'; @@ -18,9 +19,8 @@ import { GoogleProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/ import { AuthService } from 'src/engine/core-modules/auth/services/auth.service'; import { GoogleRequest } from 'src/engine/core-modules/auth/strategies/google.auth.strategy'; import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service'; -import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; +import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Controller('auth/google') @@ -55,6 +55,7 @@ export class GoogleAuthController { workspaceInviteHash, workspacePersonalInviteToken, targetWorkspaceSubdomain, + billingCheckoutSessionState, } = req.user; const signInUpParams = { @@ -106,6 +107,7 @@ export class GoogleAuthController { this.authService.computeRedirectURI( loginToken.token, workspace.subdomain, + billingCheckoutSessionState, ), ); } catch (err) { diff --git a/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts b/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts index 3d12f2a91..131c56207 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-auth.controller.ts @@ -11,15 +11,15 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Response } from 'express'; import { Repository } from 'typeorm'; +import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter'; import { MicrosoftOAuthGuard } from 'src/engine/core-modules/auth/guards/microsoft-oauth.guard'; import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/microsoft-provider-enabled.guard'; import { AuthService } from 'src/engine/core-modules/auth/services/auth.service'; import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy'; import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service'; -import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; +import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Controller('auth/microsoft') @@ -90,6 +90,7 @@ export class MicrosoftAuthController { this.authService.computeRedirectURI( loginToken.token, workspace.subdomain, + signInUpParams.billingCheckoutSessionState, ), ); } catch (err) { diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/google-oauth.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/google-oauth.guard.ts index 8f2f6b95c..2cb7e93a1 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/guards/google-oauth.guard.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/google-oauth.guard.ts @@ -45,6 +45,14 @@ export class GoogleOauthGuard extends AuthGuard('google') { request.params.workspaceSubdomain = request.query.workspaceSubdomain; } + if ( + request.query.billingCheckoutSessionState && + typeof request.query.billingCheckoutSessionState === 'string' + ) { + request.params.billingCheckoutSessionState = + request.query.billingCheckoutSessionState; + } + return (await super.canActivate(context)) as boolean; } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/microsoft-oauth.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/microsoft-oauth.guard.ts index 049f14789..6a2d40faf 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/guards/microsoft-oauth.guard.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/microsoft-oauth.guard.ts @@ -33,6 +33,14 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') { request.params.workspaceSubdomain = request.query.workspaceSubdomain; } + if ( + request.query.billingCheckoutSessionState && + typeof request.query.billingCheckoutSessionState === 'string' + ) { + request.params.billingCheckoutSessionState = + request.query.billingCheckoutSessionState; + } + return (await super.canActivate(context)) as boolean; } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts index a045f40de..9a17c69f8 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts @@ -28,6 +28,7 @@ import { AuthorizeApp } from 'src/engine/core-modules/auth/dto/authorize-app.ent import { AuthorizeAppInput } from 'src/engine/core-modules/auth/dto/authorize-app.input'; import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; import { ChallengeInput } from 'src/engine/core-modules/auth/dto/challenge.input'; +import { AuthTokens } from 'src/engine/core-modules/auth/dto/token.entity'; import { UpdatePassword } from 'src/engine/core-modules/auth/dto/update-password.entity'; import { UserExists, @@ -46,7 +47,6 @@ import { userValidator } from 'src/engine/core-modules/user/user.validate'; import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { AuthTokens } from 'src/engine/core-modules/auth/dto/token.entity'; @Injectable() // eslint-disable-next-line @nx/workspace-inject-workspace-repository @@ -171,6 +171,7 @@ export class AuthService { fromSSO: boolean; targetWorkspaceSubdomain?: string; authProvider?: WorkspaceAuthProvider; + billingCheckoutSessionState?: string; }) { return await this.signInUpService.signInUp({ email, @@ -413,11 +414,18 @@ export class AuthService { return workspace; } - computeRedirectURI(loginToken: string, subdomain?: string) { + computeRedirectURI( + loginToken: string, + subdomain?: string, + billingCheckoutSessionState?: string, + ) { const url = this.domainManagerService.buildWorkspaceURL({ subdomain, pathname: '/verify', - searchParams: { loginToken }, + searchParams: { + loginToken, + ...(billingCheckoutSessionState ? { billingCheckoutSessionState } : {}), + }, }); return url.toString(); diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts index e86e60a7f..53a8028cb 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts @@ -35,8 +35,8 @@ import { import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email'; import { getImageBufferFromUrl } from 'src/utils/image'; -import { isWorkEmail } from 'src/utils/is-work-email'; import { isDefined } from 'src/utils/is-defined'; +import { isWorkEmail } from 'src/utils/is-work-email'; export type SignInUpServiceInput = { email: string; diff --git a/packages/twenty-server/src/engine/core-modules/auth/strategies/google.auth.strategy.ts b/packages/twenty-server/src/engine/core-modules/auth/strategies/google.auth.strategy.ts index 5e20e1a60..b673e7449 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/strategies/google.auth.strategy.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/strategies/google.auth.strategy.ts @@ -18,6 +18,7 @@ export type GoogleRequest = Omit< workspaceInviteHash?: string; workspacePersonalInviteToken?: string; targetWorkspaceSubdomain?: string; + billingCheckoutSessionState?: string; }; }; @@ -39,6 +40,12 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { state: JSON.stringify({ workspaceInviteHash: req.params.workspaceInviteHash, workspaceSubdomain: req.params.workspaceSubdomain, + ...(req.params.billingCheckoutSessionState + ? { + billingCheckoutSessionState: + req.params.billingCheckoutSessionState, + } + : {}), ...(req.params.workspacePersonalInviteToken ? { workspacePersonalInviteToken: @@ -72,6 +79,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { workspaceInviteHash: state.workspaceInviteHash, workspacePersonalInviteToken: state.workspacePersonalInviteToken, targetWorkspaceSubdomain: state.workspaceSubdomain, + billingCheckoutSessionState: state.billingCheckoutSessionState, }; done(null, user); diff --git a/packages/twenty-server/src/engine/core-modules/auth/strategies/microsoft.auth.strategy.ts b/packages/twenty-server/src/engine/core-modules/auth/strategies/microsoft.auth.strategy.ts index 39077fb6d..1ae2afb3c 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/strategies/microsoft.auth.strategy.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/strategies/microsoft.auth.strategy.ts @@ -22,6 +22,7 @@ export type MicrosoftRequest = Omit< workspaceInviteHash?: string; workspacePersonalInviteToken?: string; targetWorkspaceSubdomain?: string; + billingCheckoutSessionState?: string; }; }; @@ -43,6 +44,12 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy, 'microsoft') { state: JSON.stringify({ workspaceInviteHash: req.params.workspaceInviteHash, workspaceSubdomain: req.params.workspaceSubdomain, + ...(req.params.billingCheckoutSessionState + ? { + billingCheckoutSessionState: + req.params.billingCheckoutSessionState, + } + : {}), ...(req.params.workspacePersonalInviteToken ? { workspacePersonalInviteToken: @@ -86,6 +93,7 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy, 'microsoft') { workspaceInviteHash: state.workspaceInviteHash, workspacePersonalInviteToken: state.workspacePersonalInviteToken, targetWorkspaceSubdomain: state.workspaceSubdomain, + billingCheckoutSessionState: state.billingCheckoutSessionState, }; done(null, user); 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 4dcc92f96..df2d4469d 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 @@ -56,7 +56,13 @@ export class BillingResolver { async checkoutSession( @AuthWorkspace() workspace: Workspace, @AuthUser() user: User, - @Args() { recurringInterval, successUrlPath }: CheckoutSessionInput, + @Args() + { + recurringInterval, + successUrlPath, + plan, + requirePaymentMethod, + }: CheckoutSessionInput, ) { const productPrice = await this.stripeService.getStripePrice( AvailableProduct.BasePlan, @@ -75,6 +81,8 @@ export class BillingResolver { workspace, productPrice.stripePriceId, successUrlPath, + plan, + requirePaymentMethod, ), }; } diff --git a/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts b/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts index 9371eee8f..b5c882be2 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/dto/checkout-session.input.ts @@ -1,16 +1,32 @@ import { ArgsType, Field } from '@nestjs/graphql'; -import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; -import Stripe from 'stripe'; +import { + IsBoolean, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; +import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum'; import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum'; @ArgsType() export class CheckoutSessionInput { @Field(() => SubscriptionInterval) - @IsString() + @IsEnum(SubscriptionInterval) @IsNotEmpty() - recurringInterval: Stripe.Price.Recurring.Interval; + recurringInterval: SubscriptionInterval; + + @Field(() => BillingPlanKey, { defaultValue: BillingPlanKey.PRO }) + @IsEnum(BillingPlanKey) + @IsOptional() + plan?: BillingPlanKey; + + @Field(() => Boolean, { defaultValue: true }) + @IsBoolean() + @IsOptional() + requirePaymentMethod?: boolean; @Field(() => String, { nullable: true }) @IsString() diff --git a/packages/twenty-server/src/engine/core-modules/billing/enums/billing-plan-key.enum.ts b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-plan-key.enum.ts index 2f8aa1eb1..55b4e2ad6 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/enums/billing-plan-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/enums/billing-plan-key.enum.ts @@ -1,4 +1,12 @@ +import { registerEnumType } from '@nestjs/graphql'; + export enum BillingPlanKey { - BASE_PLAN = 'BASE_PLAN', - PRO_PLAN = 'PRO_PLAN', + BASE = 'BASE', + PRO = 'PRO', + ENTERPRISE = 'ENTERPRISE', } + +registerEnumType(BillingPlanKey, { + name: 'BillingPlanKey', + description: 'The different billing plans available', +}); 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 44b26626d..f422b07dc 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 @@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; +import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; @@ -30,6 +31,8 @@ export class BillingPortalWorkspaceService { workspace: Workspace, priceId: string, successUrlPath?: string, + plan?: BillingPlanKey, + requirePaymentMethod?: boolean, ): Promise { const frontBaseUrl = this.domainManagerService.getBaseUrl(); const cancelUrl = frontBaseUrl.toString(); @@ -57,6 +60,8 @@ export class BillingPortalWorkspaceService { successUrl, cancelUrl, stripeCustomerId, + plan, + requirePaymentMethod, ); assert(session.url, 'Error: missing checkout.session.url'); diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook-product.service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook-product.service.ts index 1c9fcf859..084a4759b 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook-product.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-webhook-product.service.ts @@ -52,9 +52,9 @@ export class BillingWebhookProductService { isValidBillingPlanKey(planKey?: string) { switch (planKey) { - case BillingPlanKey.BASE_PLAN: + case BillingPlanKey.BASE: return true; - case BillingPlanKey.PRO_PLAN: + case BillingPlanKey.PRO: return true; default: return false; diff --git a/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts b/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts index 62d66fb79..cd837ae8f 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/stripe/stripe.service.ts @@ -5,6 +5,7 @@ import Stripe from 'stripe'; import { ProductPriceEntity } from 'src/engine/core-modules/billing/dto/product-price.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum'; +import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { User } from 'src/engine/core-modules/user/user.entity'; @@ -90,6 +91,8 @@ export class StripeService { successUrl?: string, cancelUrl?: string, stripeCustomerId?: string, + plan: BillingPlanKey = BillingPlanKey.PRO, + requirePaymentMethod = true, ): Promise { return await this.stripe.checkout.sessions.create({ line_items: [ @@ -102,18 +105,22 @@ export class StripeService { subscription_data: { metadata: { workspaceId, + plan, }, trial_period_days: this.environmentService.get( 'BILLING_FREE_TRIAL_DURATION_IN_DAYS', ), }, - automatic_tax: { enabled: true }, - tax_id_collection: { enabled: true }, + automatic_tax: { enabled: !!requirePaymentMethod }, + tax_id_collection: { enabled: !!requirePaymentMethod }, customer: stripeCustomerId, customer_update: stripeCustomerId ? { name: 'auto' } : undefined, customer_email: stripeCustomerId ? undefined : user.email, success_url: successUrl, cancel_url: cancelUrl, + payment_method_collection: requirePaymentMethod + ? 'always' + : 'if_required', }); } diff --git a/yarn.lock b/yarn.lock index 00f102542..0b46335c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11576,6 +11576,13 @@ __metadata: languageName: node linkType: hard +"@recoiljs/refine@npm:^0.1.1": + version: 0.1.1 + resolution: "@recoiljs/refine@npm:0.1.1" + checksum: 10c0/27ca1c4ea500b1b99a3af5ee48d6749310e5138e83b87ddfb41304e2222fa64567acb985f340334ab73980202ab277a0f133c40817fbec786076c06bfb3f5363 + languageName: node + linkType: hard + "@redis/bloom@npm:1.2.0, @redis/bloom@npm:^1.2.0": version: 1.2.0 resolution: "@redis/bloom@npm:1.2.0" @@ -40639,6 +40646,18 @@ __metadata: languageName: node linkType: hard +"recoil-sync@npm:^0.2.0": + version: 0.2.0 + resolution: "recoil-sync@npm:0.2.0" + dependencies: + "@recoiljs/refine": "npm:^0.1.1" + transit-js: "npm:^0.8.874" + peerDependencies: + recoil: ">=0.7.3" + checksum: 10c0/f3a671a3cfcecadb5bbb22d47b3b040721be1013ee91d4fa0570a64ca1707eb68e818b291a82e133551adf4a2dd2fc9a0715d550c1f1e3db82f3b8d9169e2a5a + languageName: node + linkType: hard + "recoil@npm:^0.7.7": version: 0.7.7 resolution: "recoil@npm:0.7.7" @@ -44346,6 +44365,13 @@ __metadata: languageName: node linkType: hard +"transit-js@npm:^0.8.874": + version: 0.8.874 + resolution: "transit-js@npm:0.8.874" + checksum: 10c0/6ca0b413f1e3780a4a56b9bbde54b67a2ffefda1c052f8dc68bf28c1a3df4c29baa6f17f60484c9f2fb056a8c7dcd1fe04d24e091fb99afe1525e4d4406ad58a + languageName: node + linkType: hard + "transliteration@npm:^2.3.5": version: 2.3.5 resolution: "transliteration@npm:2.3.5" @@ -44879,6 +44905,7 @@ __metadata: buffer: "npm:^6.0.3" docx: "npm:^9.1.0" file-saver: "npm:^2.0.5" + recoil-sync: "npm:^0.2.0" transliteration: "npm:^2.3.5" languageName: unknown linkType: soft