Pass Billing Checkout var in url to bypass credit card (#9283)

This commit is contained in:
Félix Malfait
2024-12-31 14:48:00 +01:00
committed by GitHub
parent 45f14c8020
commit 97f5a5b8a5
123 changed files with 524 additions and 173 deletions

View File

@ -31,6 +31,13 @@ We felt the need for a CRM platform that empowers rather than constrains. We bel
<br> <br>
# Demo # Demo
<!--
You can use the following url to sign up to the cloud version without providing a credit card:
<a href="https://demo.twenty.com/?billingCheckoutSession={"plan":"PRO","recurringInterval":"MONTHLY","requirePaymentMethod":false,"skipPlanPage":true}">https://demo.twenty.com/?billingCheckoutSession={"plan":"PRO","recurringInterval":"MONTHLY","requirePaymentMethod":false,"skipPlanPage":true}</a>
-->
Go to <a href="https://demo.twenty.com/">demo.twenty.com</a> and login with the following credentials: Go to <a href="https://demo.twenty.com/">demo.twenty.com</a> and login with the following credentials:
``` ```

View File

@ -48,6 +48,7 @@
"buffer": "^6.0.3", "buffer": "^6.0.3",
"docx": "^9.1.0", "docx": "^9.1.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"recoil-sync": "^0.2.0",
"transliteration": "^2.3.5" "transliteration": "^2.3.5"
}, },
"devDependencies": { "devDependencies": {

View File

@ -115,6 +115,13 @@ export type Billing = {
isBillingEnabled: Scalars['Boolean']; isBillingEnabled: Scalars['Boolean'];
}; };
/** The different billing plans available */
export enum BillingPlanKey {
Base = 'BASE',
Enterprise = 'ENTERPRISE',
Pro = 'PRO'
}
export type BillingSubscription = { export type BillingSubscription = {
__typename?: 'BillingSubscription'; __typename?: 'BillingSubscription';
id: Scalars['UUID']; id: Scalars['UUID'];
@ -182,6 +189,30 @@ export type ComputeStepOutputSchemaInput = {
step: Scalars['JSON']; step: Scalars['JSON'];
}; };
export type CreateFieldInput = {
defaultValue?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
icon?: InputMaybe<Scalars['String']>;
isActive?: InputMaybe<Scalars['Boolean']>;
isCustom?: InputMaybe<Scalars['Boolean']>;
isLabelSyncedWithName?: InputMaybe<Scalars['Boolean']>;
isNullable?: InputMaybe<Scalars['Boolean']>;
isRemoteCreation?: InputMaybe<Scalars['Boolean']>;
isSystem?: InputMaybe<Scalars['Boolean']>;
isUnique?: InputMaybe<Scalars['Boolean']>;
label: Scalars['String'];
name: Scalars['String'];
objectMetadataId: Scalars['String'];
options?: InputMaybe<Scalars['JSON']>;
settings?: InputMaybe<Scalars['JSON']>;
type: FieldMetadataType;
};
export type CreateOneFieldMetadataInput = {
/** The record to create */
field: CreateFieldInput;
};
export type CreateServerlessFunctionInput = { export type CreateServerlessFunctionInput = {
description?: InputMaybe<Scalars['String']>; description?: InputMaybe<Scalars['String']>;
name: Scalars['String']; name: Scalars['String'];
@ -205,6 +236,11 @@ export type CursorPaging = {
last?: InputMaybe<Scalars['Int']>; last?: InputMaybe<Scalars['Int']>;
}; };
export type DeleteOneFieldInput = {
/** The id of the field to delete. */
id: Scalars['UUID'];
};
export type DeleteOneObjectInput = { export type DeleteOneObjectInput = {
/** The id of the record to delete. */ /** The id of the record to delete. */
id: Scalars['UUID']; id: Scalars['UUID'];
@ -447,12 +483,14 @@ export type Mutation = {
computeStepOutputSchema: Scalars['JSON']; computeStepOutputSchema: Scalars['JSON'];
createOIDCIdentityProvider: SetupSsoOutput; createOIDCIdentityProvider: SetupSsoOutput;
createOneAppToken: AppToken; createOneAppToken: AppToken;
createOneField: Field;
createOneObject: Object; createOneObject: Object;
createOneServerlessFunction: ServerlessFunction; createOneServerlessFunction: ServerlessFunction;
createSAMLIdentityProvider: SetupSsoOutput; createSAMLIdentityProvider: SetupSsoOutput;
createWorkflowVersionStep: WorkflowAction; createWorkflowVersionStep: WorkflowAction;
deactivateWorkflowVersion: Scalars['Boolean']; deactivateWorkflowVersion: Scalars['Boolean'];
deleteCurrentWorkspace: Workspace; deleteCurrentWorkspace: Workspace;
deleteOneField: Field;
deleteOneObject: Object; deleteOneObject: Object;
deleteOneServerlessFunction: ServerlessFunction; deleteOneServerlessFunction: ServerlessFunction;
deleteSSOIdentityProvider: DeleteSsoOutput; deleteSSOIdentityProvider: DeleteSsoOutput;
@ -478,6 +516,7 @@ export type Mutation = {
switchWorkspace: PublicWorkspaceDataOutput; switchWorkspace: PublicWorkspaceDataOutput;
track: Analytics; track: Analytics;
updateBillingSubscription: UpdateBillingEntity; updateBillingSubscription: UpdateBillingEntity;
updateOneField: Field;
updateOneObject: Object; updateOneObject: Object;
updateOneServerlessFunction: ServerlessFunction; updateOneServerlessFunction: ServerlessFunction;
updatePasswordViaResetToken: InvalidatePassword; updatePasswordViaResetToken: InvalidatePassword;
@ -528,7 +567,9 @@ export type MutationChallengeArgs = {
export type MutationCheckoutSessionArgs = { export type MutationCheckoutSessionArgs = {
plan?: BillingPlanKey;
recurringInterval: SubscriptionInterval; recurringInterval: SubscriptionInterval;
requirePaymentMethod?: Scalars['Boolean'];
successUrlPath?: InputMaybe<Scalars['String']>; successUrlPath?: InputMaybe<Scalars['String']>;
}; };
@ -543,6 +584,11 @@ export type MutationCreateOidcIdentityProviderArgs = {
}; };
export type MutationCreateOneFieldArgs = {
input: CreateOneFieldMetadataInput;
};
export type MutationCreateOneServerlessFunctionArgs = { export type MutationCreateOneServerlessFunctionArgs = {
input: CreateServerlessFunctionInput; input: CreateServerlessFunctionInput;
}; };
@ -563,6 +609,11 @@ export type MutationDeactivateWorkflowVersionArgs = {
}; };
export type MutationDeleteOneFieldArgs = {
input: DeleteOneFieldInput;
};
export type MutationDeleteOneObjectArgs = { export type MutationDeleteOneObjectArgs = {
input: DeleteOneObjectInput; input: DeleteOneObjectInput;
}; };
@ -665,6 +716,11 @@ export type MutationTrackArgs = {
}; };
export type MutationUpdateOneFieldArgs = {
input: UpdateOneFieldMetadataInput;
};
export type MutationUpdateOneObjectArgs = { export type MutationUpdateOneObjectArgs = {
input: UpdateOneObjectInput; input: UpdateOneObjectInput;
}; };
@ -825,6 +881,8 @@ export type Query = {
clientConfig: ClientConfig; clientConfig: ClientConfig;
currentUser: User; currentUser: User;
currentWorkspace: Workspace; currentWorkspace: Workspace;
field: Field;
fields: FieldConnection;
findAvailableWorkspacesByEmail: Array<AvailableWorkspaceOutput>; findAvailableWorkspacesByEmail: Array<AvailableWorkspaceOutput>;
findManyServerlessFunctions: Array<ServerlessFunction>; findManyServerlessFunctions: Array<ServerlessFunction>;
findOneServerlessFunction: ServerlessFunction; findOneServerlessFunction: ServerlessFunction;
@ -1241,6 +1299,22 @@ export type UpdateBillingEntity = {
success: Scalars['Boolean']; success: Scalars['Boolean'];
}; };
export type UpdateFieldInput = {
defaultValue?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
icon?: InputMaybe<Scalars['String']>;
isActive?: InputMaybe<Scalars['Boolean']>;
isCustom?: InputMaybe<Scalars['Boolean']>;
isLabelSyncedWithName?: InputMaybe<Scalars['Boolean']>;
isNullable?: InputMaybe<Scalars['Boolean']>;
isSystem?: InputMaybe<Scalars['Boolean']>;
isUnique?: InputMaybe<Scalars['Boolean']>;
label?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
options?: InputMaybe<Scalars['JSON']>;
settings?: InputMaybe<Scalars['JSON']>;
};
export type UpdateObjectPayload = { export type UpdateObjectPayload = {
description?: InputMaybe<Scalars['String']>; description?: InputMaybe<Scalars['String']>;
icon?: InputMaybe<Scalars['String']>; icon?: InputMaybe<Scalars['String']>;
@ -1255,6 +1329,13 @@ export type UpdateObjectPayload = {
shortcut?: InputMaybe<Scalars['String']>; shortcut?: InputMaybe<Scalars['String']>;
}; };
export type UpdateOneFieldMetadataInput = {
/** The id of the record to update */
id: Scalars['UUID'];
/** The record to update */
update: UpdateFieldInput;
};
export type UpdateOneObjectInput = { export type UpdateOneObjectInput = {
/** The id of the object to update */ /** The id of the object to update */
id: Scalars['UUID']; id: Scalars['UUID'];
@ -1944,6 +2025,8 @@ export type BillingPortalSessionQuery = { __typename?: 'Query', billingPortalSes
export type CheckoutSessionMutationVariables = Exact<{ export type CheckoutSessionMutationVariables = Exact<{
recurringInterval: SubscriptionInterval; recurringInterval: SubscriptionInterval;
successUrlPath?: InputMaybe<Scalars['String']>; successUrlPath?: InputMaybe<Scalars['String']>;
plan: BillingPlanKey;
requirePaymentMethod: Scalars['Boolean'];
}>; }>;
@ -3234,10 +3317,12 @@ export type BillingPortalSessionQueryHookResult = ReturnType<typeof useBillingPo
export type BillingPortalSessionLazyQueryHookResult = ReturnType<typeof useBillingPortalSessionLazyQuery>; export type BillingPortalSessionLazyQueryHookResult = ReturnType<typeof useBillingPortalSessionLazyQuery>;
export type BillingPortalSessionQueryResult = Apollo.QueryResult<BillingPortalSessionQuery, BillingPortalSessionQueryVariables>; export type BillingPortalSessionQueryResult = Apollo.QueryResult<BillingPortalSessionQuery, BillingPortalSessionQueryVariables>;
export const CheckoutSessionDocument = gql` export const CheckoutSessionDocument = gql`
mutation CheckoutSession($recurringInterval: SubscriptionInterval!, $successUrlPath: String) { mutation CheckoutSession($recurringInterval: SubscriptionInterval!, $successUrlPath: String, $plan: BillingPlanKey!, $requirePaymentMethod: Boolean!) {
checkoutSession( checkoutSession(
recurringInterval: $recurringInterval recurringInterval: $recurringInterval
successUrlPath: $successUrlPath successUrlPath: $successUrlPath
plan: $plan
requirePaymentMethod: $requirePaymentMethod
) { ) {
url url
} }
@ -3260,6 +3345,8 @@ export type CheckoutSessionMutationFn = Apollo.MutationFunction<CheckoutSessionM
* variables: { * variables: {
* recurringInterval: // value for 'recurringInterval' * recurringInterval: // value for 'recurringInterval'
* successUrlPath: // value for 'successUrlPath' * successUrlPath: // value for 'successUrlPath'
* plan: // value for 'plan'
* requirePaymentMethod: // value for 'requirePaymentMethod'
* }, * },
* }); * });
*/ */

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const copilotQueryState = createState({ export const copilotQueryState = createState({
key: 'activities/copilot-query', key: 'activities/copilot-query',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const emailThreadIdWhenEmailThreadWasClosedState = createState< export const emailThreadIdWhenEmailThreadWasClosedState = createState<
string | null string | null

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const canCreateActivityState = createState<boolean>({ export const canCreateActivityState = createState<boolean>({
key: 'canCreateActivityState', key: 'canCreateActivityState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isActivityInCreateModeState = createState<boolean>({ export const isActivityInCreateModeState = createState<boolean>({
key: 'isActivityInCreateModeState', key: 'isActivityInCreateModeState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isUpsertingActivityInDBState = createState<boolean>({ export const isUpsertingActivityInDBState = createState<boolean>({
key: 'isUpsertingActivityInDBState', key: 'isUpsertingActivityInDBState',

View File

@ -7,26 +7,29 @@ import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHa
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
import { HelmetProvider } from 'react-helmet-async'; import { HelmetProvider } from 'react-helmet-async';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { RecoilURLSyncJSON } from 'recoil-sync';
import { IconsProvider } from 'twenty-ui'; import { IconsProvider } from 'twenty-ui';
export const App = () => { export const App = () => {
return ( return (
<RecoilRoot> <RecoilRoot>
<AppErrorBoundary> <RecoilURLSyncJSON location={{ part: 'queryParams' }}>
<CaptchaProvider> <AppErrorBoundary>
<RecoilDebugObserverEffect /> <CaptchaProvider>
<ApolloDevLogEffect /> <RecoilDebugObserverEffect />
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> <ApolloDevLogEffect />
<IconsProvider> <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<ExceptionHandlerProvider> <IconsProvider>
<HelmetProvider> <ExceptionHandlerProvider>
<AppRouter /> <HelmetProvider>
</HelmetProvider> <AppRouter />
</ExceptionHandlerProvider> </HelmetProvider>
</IconsProvider> </ExceptionHandlerProvider>
</SnackBarProviderScope> </IconsProvider>
</CaptchaProvider> </SnackBarProviderScope>
</AppErrorBoundary> </CaptchaProvider>
</AppErrorBoundary>
</RecoilURLSyncJSON>
</RecoilRoot> </RecoilRoot>
); );
}; };

View File

@ -43,16 +43,17 @@ import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTi
import { currentUserState } from '../states/currentUserState'; import { currentUserState } from '../states/currentUserState';
import { tokenPairState } from '../states/tokenPairState'; import { tokenPairState } from '../states/tokenPairState';
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain'; import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain'; import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation'; 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 { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState'; import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
export const useAuth = () => { export const useAuth = () => {
const setTokenPair = useSetRecoilState(tokenPairState); const setTokenPair = useSetRecoilState(tokenPairState);
@ -382,6 +383,7 @@ export const useAuth = () => {
params: { params: {
workspacePersonalInviteToken?: string; workspacePersonalInviteToken?: string;
workspaceInviteHash?: string; workspaceInviteHash?: string;
billingCheckoutSession?: BillingCheckoutSession;
}, },
) => { ) => {
const url = new URL(`${REACT_APP_SERVER_BASE_URL}${path}`); const url = new URL(`${REACT_APP_SERVER_BASE_URL}${path}`);
@ -394,6 +396,12 @@ export const useAuth = () => {
params.workspacePersonalInviteToken, params.workspacePersonalInviteToken,
); );
} }
if (isDefined(params.billingCheckoutSession)) {
url.searchParams.set(
'billingCheckoutSessionState',
JSON.stringify(params.billingCheckoutSession),
);
}
if (isDefined(workspaceSubdomain)) { if (isDefined(workspaceSubdomain)) {
url.searchParams.set('workspaceSubdomain', workspaceSubdomain); url.searchParams.set('workspaceSubdomain', workspaceSubdomain);
@ -408,6 +416,7 @@ export const useAuth = () => {
(params: { (params: {
workspacePersonalInviteToken?: string; workspacePersonalInviteToken?: string;
workspaceInviteHash?: string; workspaceInviteHash?: string;
billingCheckoutSession?: BillingCheckoutSession;
}) => { }) => {
redirect(buildRedirectUrl('/auth/google', params)); redirect(buildRedirectUrl('/auth/google', params));
}, },
@ -418,6 +427,7 @@ export const useAuth = () => {
(params: { (params: {
workspacePersonalInviteToken?: string; workspacePersonalInviteToken?: string;
workspaceInviteHash?: string; workspaceInviteHash?: string;
billingCheckoutSession?: BillingCheckoutSession;
}) => { }) => {
redirect(buildRedirectUrl('/auth/microsoft', params)); redirect(buildRedirectUrl('/auth/microsoft', params));
}, },

View File

@ -1,7 +1,9 @@
import { renderHook } from '@testing-library/react';
import { useParams, useSearchParams } from 'react-router-dom';
import { useAuth } from '@/auth/hooks/useAuth'; import { useAuth } from '@/auth/hooks/useAuth';
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle'; 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', () => ({ jest.mock('react-router-dom', () => ({
useParams: jest.fn(), useParams: jest.fn(),
@ -13,10 +15,24 @@ jest.mock('@/auth/hooks/useAuth', () => ({
})); }));
describe('useSignInWithGoogle', () => { 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', () => { it('should call signInWithGoogle with correct params', () => {
const signInWithGoogleMock = jest.fn(); const signInWithGoogleMock = jest.fn();
const mockUseParams = { workspaceInviteHash: 'testHash' }; 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); (useParams as jest.Mock).mockReturnValue(mockUseParams);
(useSearchParams as jest.Mock).mockReturnValue([mockSearchParams]); (useSearchParams as jest.Mock).mockReturnValue([mockSearchParams]);
@ -24,12 +40,15 @@ describe('useSignInWithGoogle', () => {
signInWithGoogle: signInWithGoogleMock, signInWithGoogle: signInWithGoogleMock,
}); });
const { result } = renderHook(() => useSignInWithGoogle()); const { result } = renderHook(() => useSignInWithGoogle(), {
wrapper: Wrapper,
});
result.current.signInWithGoogle(); result.current.signInWithGoogle();
expect(signInWithGoogleMock).toHaveBeenCalledWith({ expect(signInWithGoogleMock).toHaveBeenCalledWith({
workspaceInviteHash: 'testHash', workspaceInviteHash: 'testHash',
workspacePersonalInviteToken: 'testToken', workspacePersonalInviteToken: 'testToken',
billingCheckoutSession: mockBillingCheckoutSession,
}); });
}); });
@ -44,12 +63,15 @@ describe('useSignInWithGoogle', () => {
signInWithGoogle: signInWithGoogleMock, signInWithGoogle: signInWithGoogleMock,
}); });
const { result } = renderHook(() => useSignInWithGoogle()); const { result } = renderHook(() => useSignInWithGoogle(), {
wrapper: Wrapper,
});
result.current.signInWithGoogle(); result.current.signInWithGoogle();
expect(signInWithGoogleMock).toHaveBeenCalledWith({ expect(signInWithGoogleMock).toHaveBeenCalledWith({
workspaceInviteHash: 'testHash', workspaceInviteHash: 'testHash',
workspacePersonalInviteToken: undefined, workspacePersonalInviteToken: undefined,
billingCheckoutSession: mockBillingCheckoutSession,
}); });
}); });
}); });

View File

@ -1,7 +1,8 @@
import { renderHook } from '@testing-library/react';
import { useParams, useSearchParams } from 'react-router-dom';
import { useAuth } from '@/auth/hooks/useAuth'; import { useAuth } from '@/auth/hooks/useAuth';
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft'; 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', () => ({ jest.mock('react-router-dom', () => ({
useParams: jest.fn(), useParams: jest.fn(),
@ -13,6 +14,17 @@ jest.mock('@/auth/hooks/useAuth', () => ({
})); }));
describe('useSignInWithMicrosoft', () => { describe('useSignInWithMicrosoft', () => {
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});
const mockBillingCheckoutSession = {
plan: 'PRO',
interval: 'Month',
requirePaymentMethod: true,
skipPlanPage: false,
};
it('should call signInWithMicrosoft with the correct parameters', () => { it('should call signInWithMicrosoft with the correct parameters', () => {
const workspaceInviteHashMock = 'testHash'; const workspaceInviteHashMock = 'testHash';
const inviteTokenMock = 'testToken'; const inviteTokenMock = 'testToken';
@ -28,12 +40,15 @@ describe('useSignInWithMicrosoft', () => {
signInWithMicrosoft: signInWithMicrosoftMock, signInWithMicrosoft: signInWithMicrosoftMock,
}); });
const { result } = renderHook(() => useSignInWithMicrosoft()); const { result } = renderHook(() => useSignInWithMicrosoft(), {
wrapper: Wrapper,
});
result.current.signInWithMicrosoft(); result.current.signInWithMicrosoft();
expect(signInWithMicrosoftMock).toHaveBeenCalledWith({ expect(signInWithMicrosoftMock).toHaveBeenCalledWith({
workspaceInviteHash: workspaceInviteHashMock, workspaceInviteHash: workspaceInviteHashMock,
workspacePersonalInviteToken: inviteTokenMock, workspacePersonalInviteToken: inviteTokenMock,
billingCheckoutSession: mockBillingCheckoutSession,
}); });
}); });
@ -49,10 +64,13 @@ describe('useSignInWithMicrosoft', () => {
signInWithMicrosoft: signInWithMicrosoftMock, signInWithMicrosoft: signInWithMicrosoftMock,
}); });
const { result } = renderHook(() => useSignInWithMicrosoft()); const { result } = renderHook(() => useSignInWithMicrosoft(), {
wrapper: Wrapper,
});
result.current.signInWithMicrosoft(); result.current.signInWithMicrosoft();
expect(signInWithMicrosoftMock).toHaveBeenCalledWith({ expect(signInWithMicrosoftMock).toHaveBeenCalledWith({
billingCheckoutSession: mockBillingCheckoutSession,
workspaceInviteHash: workspaceInviteHashMock, workspaceInviteHash: workspaceInviteHashMock,
workspacePersonalInviteToken: undefined, workspacePersonalInviteToken: undefined,
}); });

View File

@ -1,15 +1,27 @@
import { useParams, useSearchParams } from 'react-router-dom'; import { useParams, useSearchParams } from 'react-router-dom';
import { useAuth } from '@/auth/hooks/useAuth'; import { useAuth } from '@/auth/hooks/useAuth';
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
export const useSignInWithGoogle = () => { export const useSignInWithGoogle = () => {
const workspaceInviteHash = useParams().workspaceInviteHash; const workspaceInviteHash = useParams().workspaceInviteHash;
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const workspacePersonalInviteToken = const workspacePersonalInviteToken =
searchParams.get('inviteToken') ?? undefined; searchParams.get('inviteToken') ?? undefined;
const billingCheckoutSession = {
plan: 'PRO',
interval: 'Month',
requirePaymentMethod: true,
skipPlanPage: false,
} as BillingCheckoutSession;
const { signInWithGoogle } = useAuth(); const { signInWithGoogle } = useAuth();
return { return {
signInWithGoogle: () => signInWithGoogle: () =>
signInWithGoogle({ workspaceInviteHash, workspacePersonalInviteToken }), signInWithGoogle({
workspaceInviteHash,
workspacePersonalInviteToken,
billingCheckoutSession,
}),
}; };
}; };

View File

@ -1,18 +1,23 @@
import { useParams, useSearchParams } from 'react-router-dom'; import { useParams, useSearchParams } from 'react-router-dom';
import { useAuth } from '@/auth/hooks/useAuth'; import { useAuth } from '@/auth/hooks/useAuth';
import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState';
import { useRecoilValue } from 'recoil';
export const useSignInWithMicrosoft = () => { export const useSignInWithMicrosoft = () => {
const workspaceInviteHash = useParams().workspaceInviteHash; const workspaceInviteHash = useParams().workspaceInviteHash;
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const workspacePersonalInviteToken = const workspacePersonalInviteToken =
searchParams.get('inviteToken') ?? undefined; searchParams.get('inviteToken') ?? undefined;
const billingCheckoutSession = useRecoilValue(billingCheckoutSessionState);
const { signInWithMicrosoft } = useAuth(); const { signInWithMicrosoft } = useAuth();
return { return {
signInWithMicrosoft: () => signInWithMicrosoft: () =>
signInWithMicrosoft({ signInWithMicrosoft({
workspaceInviteHash, workspaceInviteHash,
workspacePersonalInviteToken, workspacePersonalInviteToken,
billingCheckoutSession,
}), }),
}; };
}; };

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { UserExists } from '~/generated/graphql'; import { UserExists } from '~/generated/graphql';
export const availableSSOIdentityProvidersForAuthState = createState< export const availableSSOIdentityProvidersForAuthState = createState<

View File

@ -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<BillingCheckoutSession>({
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;
},
}),
],
});

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { User } from '~/generated/graphql'; import { User } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';

View File

@ -1,5 +1,5 @@
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const currentWorkspaceMembersState = createState< export const currentWorkspaceMembersState = createState<
CurrentWorkspaceMember[] CurrentWorkspaceMember[]

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { Workspace } from '~/generated/graphql'; import { Workspace } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isCurrentUserLoadedState = createState<boolean>({ export const isCurrentUserLoadedState = createState<boolean>({
key: 'isCurrentUserLoadedState', key: 'isCurrentUserLoadedState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isVerifyPendingState = createState<boolean>({ export const isVerifyPendingState = createState<boolean>({
key: 'isVerifyPendingState', key: 'isVerifyPendingState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const previousUrlState = createState<string>({ export const previousUrlState = createState<string>({
key: 'previousUrlState', key: 'previousUrlState',

View File

@ -1,5 +1,5 @@
import { createState } from 'twenty-ui';
import { SignInUpMode } from '@/auth/types/signInUpMode'; import { SignInUpMode } from '@/auth/types/signInUpMode';
import { createState } from '@ui/utilities/state/utils/createState';
export const signInUpModeState = createState<SignInUpMode>({ export const signInUpModeState = createState<SignInUpMode>({
key: 'signInUpModeState', key: 'signInUpModeState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export enum SignInUpStep { export enum SignInUpStep {
Init = 'init', Init = 'init',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { AuthTokenPair } from '~/generated/graphql'; import { AuthTokenPair } from '~/generated/graphql';
import { cookieStorageEffect } from '~/utils/recoil-effects'; import { cookieStorageEffect } from '~/utils/recoil-effects';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { PublicWorkspaceDataOutput } from '~/generated/graphql'; import { PublicWorkspaceDataOutput } from '~/generated/graphql';
export const workspacePublicDataState = export const workspacePublicDataState =

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { Workspace } from '~/generated/graphql'; import { Workspace } from '~/generated/graphql';

View File

@ -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;
};

View File

@ -4,10 +4,14 @@ export const CHECKOUT_SESSION = gql`
mutation CheckoutSession( mutation CheckoutSession(
$recurringInterval: SubscriptionInterval! $recurringInterval: SubscriptionInterval!
$successUrlPath: String $successUrlPath: String
$plan: BillingPlanKey!
$requirePaymentMethod: Boolean!
) { ) {
checkoutSession( checkoutSession(
recurringInterval: $recurringInterval recurringInterval: $recurringInterval
successUrlPath: $successUrlPath successUrlPath: $successUrlPath
plan: $plan
requirePaymentMethod: $requirePaymentMethod
) { ) {
url url
} }

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const captchaTokenState = createState<string | undefined>({ export const captchaTokenState = createState<string | undefined>({
key: 'captchaTokenState', key: 'captchaTokenState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isCaptchaScriptLoadedState = createState<boolean>({ export const isCaptchaScriptLoadedState = createState<boolean>({
key: 'isCaptchaScriptLoadedState', key: 'isCaptchaScriptLoadedState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isRequestingCaptchaTokenState = createState<boolean>({ export const isRequestingCaptchaTokenState = createState<boolean>({
key: 'isRequestingCaptchaTokenState', key: 'isRequestingCaptchaTokenState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isLoadingTokensFromExtensionState = createState<boolean | null>({ export const isLoadingTokensFromExtensionState = createState<boolean | null>({
key: 'isLoadingTokensFromExtensionState', key: 'isLoadingTokensFromExtensionState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { ApiConfig } from '~/generated/graphql'; import { ApiConfig } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { AuthProviders } from '~/generated/graphql'; import { AuthProviders } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { Billing } from '~/generated/graphql'; import { Billing } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { Captcha } from '~/generated/graphql'; import { Captcha } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const chromeExtensionIdState = createState<string | null | undefined>({ export const chromeExtensionIdState = createState<string | null | undefined>({
key: 'chromeExtensionIdState', key: 'chromeExtensionIdState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
type ClientConfigApiStatus = { type ClientConfigApiStatus = {
isLoaded: boolean; isLoaded: boolean;

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isAnalyticsEnabledState = createState<boolean>({ export const isAnalyticsEnabledState = createState<boolean>({
key: 'isAnalyticsEnabled', key: 'isAnalyticsEnabled',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isDebugModeState = createState<boolean>({ export const isDebugModeState = createState<boolean>({
key: 'isDebugModeState', key: 'isDebugModeState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isDeveloperDefaultSignInPrefilledState = createState<boolean>({ export const isDeveloperDefaultSignInPrefilledState = createState<boolean>({
key: 'isDeveloperDefaultSignInPrefilledState', key: 'isDeveloperDefaultSignInPrefilledState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isMultiWorkspaceEnabledState = createState<boolean>({ export const isMultiWorkspaceEnabledState = createState<boolean>({
key: 'isMultiWorkspaceEnabled', key: 'isMultiWorkspaceEnabled',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isSSOEnabledState = createState<boolean>({ export const isSSOEnabledState = createState<boolean>({
key: 'isSSOEnabledState', key: 'isSSOEnabledState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { Sentry } from '~/generated/graphql'; import { Sentry } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { Support } from '~/generated/graphql'; import { Support } from '~/generated/graphql';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const commandMenuSearchState = createState<string>({ export const commandMenuSearchState = createState<string>({
key: 'command-menu/commandMenuSearchState', key: 'command-menu/commandMenuSearchState',

View File

@ -1,5 +1,5 @@
import { CONTEXT_STORE_INSTANCE_ID_DEFAULT_VALUE } from '@/context-store/constants/ContextStoreInstanceIdDefaultValue'; 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<string>({ export const mainContextStoreComponentInstanceIdState = createState<string>({
key: 'mainContextStoreComponentInstanceIdState', key: 'mainContextStoreComponentInstanceIdState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { ClientConfig } from '~/generated/graphql'; import { ClientConfig } from '~/generated/graphql';
export const domainConfigurationState = createState< export const domainConfigurationState = createState<

View File

@ -1,5 +1,5 @@
import { createState } from '@ui/utilities/state/utils/createState';
import { cookieStorageEffect } from '~/utils/recoil-effects'; import { cookieStorageEffect } from '~/utils/recoil-effects';
import { createState } from 'twenty-ui';
export const lastAuthenticatedWorkspaceDomainState = createState< export const lastAuthenticatedWorkspaceDomainState = createState<
| { | {

View File

@ -1,7 +1,7 @@
import { DateFormat } from '@/localization/constants/DateFormat'; import { DateFormat } from '@/localization/constants/DateFormat';
import { TimeFormat } from '@/localization/constants/TimeFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat';
import { detectTimeZone } from '@/localization/utils/detectTimeZone'; import { detectTimeZone } from '@/localization/utils/detectTimeZone';
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const dateTimeFormatState = createState<{ export const dateTimeFormatState = createState<{
timeZone: string; timeZone: string;

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const currentMobileNavigationDrawerState = createState< export const currentMobileNavigationDrawerState = createState<
'main' | 'settings' 'main' | 'settings'

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isAppWaitingForFreshObjectMetadataState = createState<boolean>({ export const isAppWaitingForFreshObjectMetadataState = createState<boolean>({
key: 'isAppWaitingForFreshObjectMetadataState', key: 'isAppWaitingForFreshObjectMetadataState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';

View File

@ -1,5 +1,5 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; 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 = { type AggregateOperation = {
operation: AGGREGATE_OPERATIONS | null; operation: AGGREGATE_OPERATIONS | null;

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const lastShowPageRecordIdState = createState<string | null>({ export const lastShowPageRecordIdState = createState<string | null>({
key: 'lastShowPageRecordIdState', key: 'lastShowPageRecordIdState',

View File

@ -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 { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';

View File

@ -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'; import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const recordIndexIsCompactModeActiveState = createState<boolean>({ export const recordIndexIsCompactModeActiveState = createState<boolean>({
key: 'recordIndexIsCompactModeActiveState', key: 'recordIndexIsCompactModeActiveState',

View File

@ -1,5 +1,5 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; 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 = { export type KanbanAggregateOperation = {
operation?: AGGREGATE_OPERATIONS | null; operation?: AGGREGATE_OPERATIONS | null;

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const recordIndexKanbanFieldMetadataIdState = createState<string | null>( export const recordIndexKanbanFieldMetadataIdState = createState<string | null>(
{ {

View File

@ -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'; import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';

View File

@ -1,5 +1,5 @@
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const recordIndexViewFilterGroupsState = createState<ViewFilterGroup[]>({ export const recordIndexViewFilterGroupsState = createState<ViewFilterGroup[]>({
key: 'recordIndexViewFilterGroupsState', key: 'recordIndexViewFilterGroupsState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isNewViewableRecordLoadingState = createState<boolean>({ export const isNewViewableRecordLoadingState = createState<boolean>({
key: 'activities/is-new-viewable-record-loading', key: 'activities/is-new-viewable-record-loading',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const viewableRecordIdState = createState<string | null>({ export const viewableRecordIdState = createState<string | null>({
key: 'activities/viewable-record-id', key: 'activities/viewable-record-id',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const viewableRecordNameSingularState = createState<string | null>({ export const viewableRecordNameSingularState = createState<string | null>({
key: 'activities/viewable-record-name-singular', key: 'activities/viewable-record-name-singular',

View File

@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { createState } from '@ui/utilities/state/utils/createState';
import { ReactNode, act } from 'react'; import { ReactNode, act } from 'react';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { createState } from 'twenty-ui';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';

View File

@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { createState } from '@ui/utilities/state/utils/createState';
import { ReactNode, act } from 'react'; import { ReactNode, act } from 'react';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { createState } from 'twenty-ui';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isRemoveSortingModalOpenState = createState<boolean>({ export const isRemoveSortingModalOpenState = createState<boolean>({
key: 'isRemoveSortingModalOpenState', key: 'isRemoveSortingModalOpenState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isSoftFocusUsingMouseState = createState<boolean>({ export const isSoftFocusUsingMouseState = createState<boolean>({
key: 'isSoftFocusUsingMouseState', key: 'isSoftFocusUsingMouseState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const settingsPreviewRecordIdState = createState<string | null>({ export const settingsPreviewRecordIdState = createState<string | null>({
key: 'settingsPreviewRecordIdState', key: 'settingsPreviewRecordIdState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const apiKeyTokenState = createState<string | null>({ export const apiKeyTokenState = createState<string | null>({
key: 'apiKeyTokenState', key: 'apiKeyTokenState',

View File

@ -1,7 +1,7 @@
/* @license Enterprise */ /* @license Enterprise */
import { SSOIdentityProvider } from '@/settings/security/types/SSOIdentityProvider'; import { SSOIdentityProvider } from '@/settings/security/types/SSOIdentityProvider';
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const SSOIdentitiesProvidersState = createState< export const SSOIdentitiesProvidersState = createState<
Omit<SSOIdentityProvider, '__typename'>[] Omit<SSOIdentityProvider, '__typename'>[]

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { SpreadsheetImportDialogOptions } from '../types'; import { SpreadsheetImportDialogOptions } from '../types';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const activeDropdownFocusIdState = createState<string | null>({ export const activeDropdownFocusIdState = createState<string | null>({
key: 'activeDropdownFocusIdState', key: 'activeDropdownFocusIdState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const previousDropdownFocusIdState = createState<string | null>({ export const previousDropdownFocusIdState = createState<string | null>({
key: 'previousDropdownFocusIdState', key: 'previousDropdownFocusIdState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isRightDrawerAnimationCompletedState = createState<boolean>({ export const isRightDrawerAnimationCompletedState = createState<boolean>({
key: 'isRightDrawerAnimationCompletedState', key: 'isRightDrawerAnimationCompletedState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isRightDrawerMinimizedState = createState<boolean>({ export const isRightDrawerMinimizedState = createState<boolean>({
key: 'ui/layout/is-right-drawer-minimized', key: 'ui/layout/is-right-drawer-minimized',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isRightDrawerOpenState = createState<boolean>({ export const isRightDrawerOpenState = createState<boolean>({
key: 'ui/layout/is-right-drawer-open', key: 'ui/layout/is-right-drawer-open',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { MessageThread } from '@/activities/emails/types/MessageThread'; import { MessageThread } from '@/activities/emails/types/MessageThread';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const rightDrawerCloseEventState = createState<Event | null>({ export const rightDrawerCloseEventState = createState<Event | null>({
key: 'rightDrawerCloseEventState', key: 'rightDrawerCloseEventState',

View File

@ -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'; import { RightDrawerTopBarDropdownButtons } from '@/ui/layout/right-drawer/types/RightDrawerTopBarDropdownButtons';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { RightDrawerPages } from '../types/RightDrawerPages'; import { RightDrawerPages } from '../types/RightDrawerPages';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const isDefaultLayoutAuthModalVisibleState = createState<boolean>({ export const isDefaultLayoutAuthModalVisibleState = createState<boolean>({
key: 'isDefaultLayoutAuthModalVisibleState', key: 'isDefaultLayoutAuthModalVisibleState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const navigationMemorizedUrlState = createState<string>({ export const navigationMemorizedUrlState = createState<string>({
key: 'navigationMemorizedUrlState', key: 'navigationMemorizedUrlState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export type StepsState = { export type StepsState = {
activeStep: number; activeStep: number;

View File

@ -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 { INITIAL_HOTKEYS_SCOPE } from '../../constants/InitialHotkeysScope';
import { HotkeyScope } from '../../types/HotkeyScope'; import { HotkeyScope } from '../../types/HotkeyScope';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const internalHotkeysEnabledScopesState = createState<string[]>({ export const internalHotkeysEnabledScopesState = createState<string[]>({
key: 'internalHotkeysEnabledScopesState', key: 'internalHotkeysEnabledScopesState',

View File

@ -1,5 +1,5 @@
import { createState } from '@ui/utilities/state/utils/createState';
import { Keys } from 'react-hotkeys-hook/dist/types'; import { Keys } from 'react-hotkeys-hook/dist/types';
import { createState } from 'twenty-ui';
export const pendingHotkeyState = createState<Keys | null>({ export const pendingHotkeyState = createState<Keys | null>({
key: 'pendingHotkeyState', key: 'pendingHotkeyState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
import { HotkeyScope } from '../../types/HotkeyScope'; import { HotkeyScope } from '../../types/HotkeyScope';

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const currentPageLocationState = createState<string>({ export const currentPageLocationState = createState<string>({
key: 'currentPageLocationState', key: 'currentPageLocationState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const openOverrideWorkflowDraftConfirmationModalState = export const openOverrideWorkflowDraftConfirmationModalState =
createState<boolean>({ createState<boolean>({

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const workflowCreateStepFromParentStepIdState = createState< export const workflowCreateStepFromParentStepIdState = createState<
string | undefined string | undefined

View File

@ -1,5 +1,5 @@
import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram'; import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram';
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const workflowDiagramState = createState<WorkflowDiagram | undefined>({ export const workflowDiagramState = createState<WorkflowDiagram | undefined>({
key: 'workflowDiagramState', key: 'workflowDiagramState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const workflowDiagramTriggerNodeSelectionState = createState< export const workflowDiagramTriggerNodeSelectionState = createState<
string | undefined string | undefined

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const workflowIdState = createState<string | undefined>({ export const workflowIdState = createState<string | undefined>({
key: 'workflowIdState', key: 'workflowIdState',

View File

@ -1,4 +1,4 @@
import { createState } from 'twenty-ui'; import { createState } from '@ui/utilities/state/utils/createState';
export const workflowLastCreatedStepIdState = createState<string | undefined>({ export const workflowLastCreatedStepIdState = createState<string | undefined>({
key: 'workflowLastCreatedStepIdState', key: 'workflowLastCreatedStepIdState',

Some files were not shown because too many files have changed in this diff Show More