diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 214497767..18a4b164a 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -170,6 +170,7 @@ export type ClientConfig = { frontDomain: Scalars['String']['output']; isEmailVerificationRequired: Scalars['Boolean']['output']; isMultiWorkspaceEnabled: Scalars['Boolean']['output']; + publicFeatureFlags: Array; sentry: Sentry; signInPrefilled: Scalars['Boolean']['output']; support: Support; @@ -545,8 +546,6 @@ export type Mutation = { __typename?: 'Mutation'; activateWorkflowVersion: Scalars['Boolean']['output']; activateWorkspace: Workspace; - addUserToWorkspace: User; - addUserToWorkspaceByInviteToken: User; authorizeApp: AuthorizeApp; challenge: LoginToken; checkoutSession: SessionEntity; @@ -590,12 +589,12 @@ export type Mutation = { sendInvitations: SendInvitationsOutput; signUp: SignUpOutput; skipSyncEmailOnboardingStep: OnboardingStepSuccess; - switchWorkspace: PublicWorkspaceDataOutput; syncRemoteTable: RemoteTable; syncRemoteTableSchemaChanges: RemoteTable; track: Analytics; unsyncRemoteTable: RemoteTable; updateBillingSubscription: UpdateBillingEntity; + updateLabPublicFeatureFlag: Scalars['Boolean']['output']; updateOneField: Field; updateOneObject: Object; updateOneRemoteServer: RemoteServer; @@ -623,16 +622,6 @@ export type MutationActivateWorkspaceArgs = { }; -export type MutationAddUserToWorkspaceArgs = { - inviteHash: Scalars['String']['input']; -}; - - -export type MutationAddUserToWorkspaceByInviteTokenArgs = { - inviteToken: Scalars['String']['input']; -}; - - export type MutationAuthorizeAppArgs = { clientId: Scalars['String']['input']; codeChallenge?: InputMaybe; @@ -833,11 +822,6 @@ export type MutationSignUpArgs = { }; -export type MutationSwitchWorkspaceArgs = { - workspaceId: Scalars['String']['input']; -}; - - export type MutationSyncRemoteTableArgs = { input: RemoteTableInput; }; @@ -859,6 +843,11 @@ export type MutationUnsyncRemoteTableArgs = { }; +export type MutationUpdateLabPublicFeatureFlagArgs = { + input: UpdateLabPublicFeatureFlagInput; +}; + + export type MutationUpdateOneFieldArgs = { input: UpdateOneFieldMetadataInput; }; @@ -1007,6 +996,19 @@ export type ProductPricesEntity = { totalNumberOfPrices: Scalars['Int']['output']; }; +export type PublicFeatureFlag = { + __typename?: 'PublicFeatureFlag'; + key: FeatureFlagKey; + metadata: PublicFeatureFlagMetadata; +}; + +export type PublicFeatureFlagMetadata = { + __typename?: 'PublicFeatureFlagMetadata'; + description: Scalars['String']['output']; + imagePath: Scalars['String']['output']; + label: Scalars['String']['output']; +}; + export type PublicWorkspaceDataOutput = { __typename?: 'PublicWorkspaceDataOutput'; authProviders: AuthProviders; @@ -1540,6 +1542,11 @@ export type UpdateFieldInput = { settings?: InputMaybe; }; +export type UpdateLabPublicFeatureFlagInput = { + publicFeatureFlag: Scalars['String']['input']; + value: Scalars['Boolean']['input']; +}; + export type UpdateObjectPayload = { description?: InputMaybe; icon?: InputMaybe; @@ -1713,7 +1720,7 @@ export type WorkflowRun = { export type WorkflowVersion = { __typename?: 'WorkflowVersion'; - workflowVersionId: Scalars['UUID']['output']; + id: Scalars['UUID']['output']; }; export type Workspace = { diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 5eecd24e0..7a9b6d25b 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -471,8 +471,6 @@ export type Mutation = { __typename?: 'Mutation'; activateWorkflowVersion: Scalars['Boolean']; activateWorkspace: Workspace; - addUserToWorkspace: User; - addUserToWorkspaceByInviteToken: User; authorizeApp: AuthorizeApp; challenge: LoginToken; checkoutSession: SessionEntity; @@ -512,7 +510,6 @@ export type Mutation = { sendInvitations: SendInvitationsOutput; signUp: SignUpOutput; skipSyncEmailOnboardingStep: OnboardingStepSuccess; - switchWorkspace: PublicWorkspaceDataOutput; track: Analytics; updateBillingSubscription: UpdateBillingEntity; updateLabPublicFeatureFlag: Scalars['Boolean']; @@ -542,16 +539,6 @@ export type MutationActivateWorkspaceArgs = { }; -export type MutationAddUserToWorkspaceArgs = { - inviteHash: Scalars['String']; -}; - - -export type MutationAddUserToWorkspaceByInviteTokenArgs = { - inviteToken: Scalars['String']; -}; - - export type MutationAuthorizeAppArgs = { clientId: Scalars['String']; codeChallenge?: InputMaybe; @@ -722,11 +709,6 @@ export type MutationSignUpArgs = { }; -export type MutationSwitchWorkspaceArgs = { - workspaceId: Scalars['String']; -}; - - export type MutationTrackArgs = { action: Scalars['String']; payload: Scalars['JSON']; @@ -1966,13 +1948,6 @@ export type SignUpMutationVariables = Exact<{ export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'SignUpOutput', loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, workspace: { __typename?: 'WorkspaceSubdomainAndId', id: string, subdomain: string } } }; -export type SwitchWorkspaceMutationVariables = Exact<{ - workspaceId: Scalars['String']; -}>; - - -export type SwitchWorkspaceMutation = { __typename?: 'Mutation', switchWorkspace: { __typename?: 'PublicWorkspaceDataOutput', id: string, subdomain: string, authProviders: { __typename?: 'AuthProviders', google: boolean, magicLink: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> } } }; - export type UpdatePasswordViaResetTokenMutationVariables = Exact<{ token: Scalars['String']; newPassword: Scalars['String']; @@ -2206,20 +2181,6 @@ export type GetWorkspaceInvitationsQuery = { __typename?: 'Query', findWorkspace export type WorkspaceMemberQueryFragmentFragment = { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }; -export type AddUserToWorkspaceMutationVariables = Exact<{ - inviteHash: Scalars['String']; -}>; - - -export type AddUserToWorkspaceMutation = { __typename?: 'Mutation', addUserToWorkspace: { __typename?: 'User', id: any } }; - -export type AddUserToWorkspaceByInviteTokenMutationVariables = Exact<{ - inviteToken: Scalars['String']; -}>; - - -export type AddUserToWorkspaceByInviteTokenMutation = { __typename?: 'Mutation', addUserToWorkspaceByInviteToken: { __typename?: 'User', id: any } }; - export type ActivateWorkspaceMutationVariables = Exact<{ input: ActivateWorkspaceInput; }>; @@ -2251,7 +2212,7 @@ export type GetWorkspaceFromInviteHashQueryVariables = Exact<{ }>; -export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, allowImpersonation: boolean } }; +export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, subdomain: string } }; export const TimelineCalendarEventParticipantFragmentFragmentDoc = gql` fragment TimelineCalendarEventParticipantFragment on TimelineCalendarEventParticipant { @@ -3106,53 +3067,6 @@ export function useSignUpMutation(baseOptions?: Apollo.MutationHookOptions; export type SignUpMutationResult = Apollo.MutationResult; export type SignUpMutationOptions = Apollo.BaseMutationOptions; -export const SwitchWorkspaceDocument = gql` - mutation SwitchWorkspace($workspaceId: String!) { - switchWorkspace(workspaceId: $workspaceId) { - id - subdomain - authProviders { - sso { - id - name - type - status - issuer - } - google - magicLink - password - microsoft - } - } -} - `; -export type SwitchWorkspaceMutationFn = Apollo.MutationFunction; - -/** - * __useSwitchWorkspaceMutation__ - * - * To run a mutation, you first call `useSwitchWorkspaceMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useSwitchWorkspaceMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [switchWorkspaceMutation, { data, loading, error }] = useSwitchWorkspaceMutation({ - * variables: { - * workspaceId: // value for 'workspaceId' - * }, - * }); - */ -export function useSwitchWorkspaceMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(SwitchWorkspaceDocument, options); - } -export type SwitchWorkspaceMutationHookResult = ReturnType; -export type SwitchWorkspaceMutationResult = Apollo.MutationResult; -export type SwitchWorkspaceMutationOptions = Apollo.BaseMutationOptions; export const UpdatePasswordViaResetTokenDocument = gql` mutation UpdatePasswordViaResetToken($token: String!, $newPassword: String!) { updatePasswordViaResetToken( @@ -4454,72 +4368,6 @@ export function useGetWorkspaceInvitationsLazyQuery(baseOptions?: Apollo.LazyQue export type GetWorkspaceInvitationsQueryHookResult = ReturnType; export type GetWorkspaceInvitationsLazyQueryHookResult = ReturnType; export type GetWorkspaceInvitationsQueryResult = Apollo.QueryResult; -export const AddUserToWorkspaceDocument = gql` - mutation AddUserToWorkspace($inviteHash: String!) { - addUserToWorkspace(inviteHash: $inviteHash) { - id - } -} - `; -export type AddUserToWorkspaceMutationFn = Apollo.MutationFunction; - -/** - * __useAddUserToWorkspaceMutation__ - * - * To run a mutation, you first call `useAddUserToWorkspaceMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useAddUserToWorkspaceMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [addUserToWorkspaceMutation, { data, loading, error }] = useAddUserToWorkspaceMutation({ - * variables: { - * inviteHash: // value for 'inviteHash' - * }, - * }); - */ -export function useAddUserToWorkspaceMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(AddUserToWorkspaceDocument, options); - } -export type AddUserToWorkspaceMutationHookResult = ReturnType; -export type AddUserToWorkspaceMutationResult = Apollo.MutationResult; -export type AddUserToWorkspaceMutationOptions = Apollo.BaseMutationOptions; -export const AddUserToWorkspaceByInviteTokenDocument = gql` - mutation AddUserToWorkspaceByInviteToken($inviteToken: String!) { - addUserToWorkspaceByInviteToken(inviteToken: $inviteToken) { - id - } -} - `; -export type AddUserToWorkspaceByInviteTokenMutationFn = Apollo.MutationFunction; - -/** - * __useAddUserToWorkspaceByInviteTokenMutation__ - * - * To run a mutation, you first call `useAddUserToWorkspaceByInviteTokenMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useAddUserToWorkspaceByInviteTokenMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [addUserToWorkspaceByInviteTokenMutation, { data, loading, error }] = useAddUserToWorkspaceByInviteTokenMutation({ - * variables: { - * inviteToken: // value for 'inviteToken' - * }, - * }); - */ -export function useAddUserToWorkspaceByInviteTokenMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(AddUserToWorkspaceByInviteTokenDocument, options); - } -export type AddUserToWorkspaceByInviteTokenMutationHookResult = ReturnType; -export type AddUserToWorkspaceByInviteTokenMutationResult = Apollo.MutationResult; -export type AddUserToWorkspaceByInviteTokenMutationOptions = Apollo.BaseMutationOptions; export const ActivateWorkspaceDocument = gql` mutation ActivateWorkspace($input: ActivateWorkspaceInput!) { activateWorkspace(data: $input) { @@ -4666,6 +4514,7 @@ export const GetWorkspaceFromInviteHashDocument = gql` displayName logo allowImpersonation + subdomain } } `; diff --git a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts index 104364957..3e2352d88 100644 --- a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts +++ b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts @@ -69,7 +69,7 @@ export const useApolloFactory = (options: Partial> = {}) => { setCurrentUser(null); setCurrentWorkspaceMember(null); setCurrentWorkspace(null); - setWorkspaces(null); + setWorkspaces([]); if ( !isMatchingLocation(AppPath.Verify) && !isMatchingLocation(AppPath.SignInUp) && diff --git a/packages/twenty-front/src/modules/auth/graphql/mutations/switchWorkspace.ts b/packages/twenty-front/src/modules/auth/graphql/mutations/switchWorkspace.ts deleted file mode 100644 index e4b604ded..000000000 --- a/packages/twenty-front/src/modules/auth/graphql/mutations/switchWorkspace.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { gql } from '@apollo/client'; - -export const SWITCH_WORKSPACE = gql` - mutation SwitchWorkspace($workspaceId: String!) { - switchWorkspace(workspaceId: $workspaceId) { - id - subdomain - authProviders { - sso { - id - name - type - status - issuer - } - google - magicLink - password - microsoft - } - } - } -`; diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index d9841498d..dec0be5cb 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -349,7 +349,7 @@ export const useAuth = () => { [setIsVerifyPendingState, verify, setTokenPair, loadCurrentUser], ); - const handleCrendentialsSignIn = useCallback( + const handleCredentialsSignIn = useCallback( async (email: string, password: string, captchaToken?: string) => { const { loginToken } = await handleChallenge( email, @@ -499,7 +499,7 @@ export const useAuth = () => { clearSession, signOut: handleSignOut, signUpWithCredentials: handleCredentialsSignUp, - signInWithCredentials: handleCrendentialsSignIn, + signInWithCredentials: handleCredentialsSignIn, signInWithGoogle: handleGoogleLogin, signInWithMicrosoft: handleMicrosoftLogin, }; diff --git a/packages/twenty-front/src/modules/auth/states/workspaces.ts b/packages/twenty-front/src/modules/auth/states/workspaces.ts index e38a4a146..b7e7c8758 100644 --- a/packages/twenty-front/src/modules/auth/states/workspaces.ts +++ b/packages/twenty-front/src/modules/auth/states/workspaces.ts @@ -5,9 +5,9 @@ import { Workspace } from '~/generated/graphql'; export type Workspaces = Pick< Workspace, 'id' | 'logo' | 'displayName' | 'subdomain' ->; +>[]; -export const workspacesState = createState({ +export const workspacesState = createState({ key: 'workspacesState', defaultValue: [], }); diff --git a/packages/twenty-front/src/modules/billing/hooks/useHandleCheckoutSession.ts b/packages/twenty-front/src/modules/billing/hooks/useHandleCheckoutSession.ts index 16056098b..db10b8f3c 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useHandleCheckoutSession.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useHandleCheckoutSession.ts @@ -8,6 +8,7 @@ import { } from '~/generated-metadata/graphql'; import { useCheckoutSessionMutation } from '~/generated/graphql'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; +import { useRedirect } from '@/domain-manager/hooks/useRedirect'; export const useHandleCheckoutSession = ({ recurringInterval, @@ -18,6 +19,8 @@ export const useHandleCheckoutSession = ({ plan: BillingPlanKey; requirePaymentMethod: boolean; }) => { + const { redirect } = useRedirect(); + const { enqueueSnackBar } = useSnackBar(); const [checkoutSession] = useCheckoutSessionMutation(); @@ -44,7 +47,7 @@ export const useHandleCheckoutSession = ({ ); return; } - window.location.replace(data.checkoutSession.url); + redirect(data.checkoutSession.url); }; return { isSubmitting, handleCheckoutSession }; }; diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useBuildWorkspaceUrl.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useBuildWorkspaceUrl.ts index e609849c0..606ae42b4 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useBuildWorkspaceUrl.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useBuildWorkspaceUrl.ts @@ -6,13 +6,13 @@ export const useBuildWorkspaceUrl = () => { const domainConfiguration = useRecoilValue(domainConfigurationState); const buildWorkspaceUrl = ( - subdomain?: string, + subdomain: string, pathname?: string, searchParams?: Record, ) => { const url = new URL(window.location.href); - if (isDefined(subdomain) && subdomain.length !== 0) { + if (subdomain.length !== 0) { url.hostname = `${subdomain}.${domainConfiguration.frontDomain}`; } diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx index e554aa114..c180f3123 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx @@ -1,3 +1,4 @@ +import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { SettingsPath } from '@/types/SettingsPath'; import { isDefined } from 'twenty-ui'; @@ -5,6 +6,8 @@ import { useBillingPortalSessionQuery } from '~/generated/graphql'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const InformationBannerBillingSubscriptionPaused = () => { + const { redirect } = useRedirect(); + const { data, loading } = useBillingPortalSessionQuery({ variables: { returnUrlPath: getSettingsPath(SettingsPath.Billing), @@ -13,7 +16,7 @@ export const InformationBannerBillingSubscriptionPaused = () => { const openBillingPortal = () => { if (isDefined(data) && isDefined(data.billingPortalSession.url)) { - window.location.replace(data.billingPortalSession.url); + redirect(data.billingPortalSession.url); } }; diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx index 0337d1dc0..b1cc81abd 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx @@ -3,8 +3,11 @@ import { SettingsPath } from '@/types/SettingsPath'; import { isDefined } from 'twenty-ui'; import { useBillingPortalSessionQuery } from '~/generated/graphql'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; +import { useRedirect } from '@/domain-manager/hooks/useRedirect'; export const InformationBannerFailPaymentInfo = () => { + const { redirect } = useRedirect(); + const { data, loading } = useBillingPortalSessionQuery({ variables: { returnUrlPath: getSettingsPath(SettingsPath.Billing), @@ -13,7 +16,7 @@ export const InformationBannerFailPaymentInfo = () => { const openBillingPortal = () => { if (isDefined(data) && isDefined(data.billingPortalSession.url)) { - window.location.replace(data.billingPortalSession.url); + redirect(data.billingPortalSession.url); } }; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx index b2b1d0de9..f02f6341a 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdownButton.tsx @@ -3,16 +3,13 @@ import { Workspaces } from '@/auth/states/workspaces'; import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo'; import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MulitWorkspaceDropdownId'; -import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching'; import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { Avatar, @@ -20,6 +17,7 @@ import { MenuItemSelectAvatar, UndecoratedLink, } from 'twenty-ui'; +import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; const StyledContainer = styled.div<{ isNavigationDrawerExpanded: boolean }>` align-items: center; @@ -56,7 +54,7 @@ const StyledIconChevronDown = styled(IconChevronDown)<{ disabled?: boolean }>` `; type MultiWorkspaceDropdownButtonProps = { - workspaces: Workspaces[]; + workspaces: Workspaces; }; export const MultiWorkspaceDropdownButton = ({ @@ -64,19 +62,12 @@ export const MultiWorkspaceDropdownButton = ({ }: MultiWorkspaceDropdownButtonProps) => { const theme = useTheme(); const currentWorkspace = useRecoilValue(currentWorkspaceState); + const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain(); - const [isMultiWorkspaceDropdownOpen, setToggleMultiWorkspaceDropdown] = - useState(false); - - const { switchWorkspace } = useWorkspaceSwitching(); const { buildWorkspaceUrl } = useBuildWorkspaceUrl(); - const { closeDropdown } = useDropdown(MULTI_WORKSPACE_DROPDOWN_ID); - - const handleChange = async (workspaceId: string) => { - setToggleMultiWorkspaceDropdown(!isMultiWorkspaceDropdownOpen); - closeDropdown(); - await switchWorkspace(workspaceId); + const handleChange = async (workspace: Workspaces[0]) => { + redirectToWorkspaceDomain(workspace.subdomain); }; const [isNavigationDrawerExpanded] = useRecoilState( isNavigationDrawerExpandedState, @@ -116,7 +107,7 @@ export const MultiWorkspaceDropdownButton = ({ to={buildWorkspaceUrl(workspace.subdomain)} onClick={(event) => { event?.preventDefault(); - handleChange(workspace.id); + handleChange(workspace); }} > 1; + const isMultiWorkspace = isMultiWorkspaceEnabled && workspaces.length > 1; return ( diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching.ts b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching.ts deleted file mode 100644 index 3066cde0c..000000000 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; - -import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; -import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; -import { useSwitchWorkspaceMutation } from '~/generated/graphql'; -import { isDefined } from '~/utils/isDefined'; -import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain'; -import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; - -export const useWorkspaceSwitching = () => { - const [switchWorkspaceMutation] = useSwitchWorkspaceMutation(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); - const { enqueueSnackBar } = useSnackBar(); - const { redirectToDefaultDomain } = useRedirectToDefaultDomain(); - const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain(); - - const switchWorkspace = async (workspaceId: string) => { - if (currentWorkspace?.id === workspaceId) return; - - if (!isMultiWorkspaceEnabled) { - return enqueueSnackBar( - 'Switching workspace is not available in single workspace mode', - { - variant: SnackBarVariant.Error, - }, - ); - } - - const { data, errors } = await switchWorkspaceMutation({ - variables: { - workspaceId, - }, - }); - - if (isDefined(errors) || !isDefined(data?.switchWorkspace.subdomain)) { - return redirectToDefaultDomain(); - } - - redirectToWorkspaceDomain(data.switchWorkspace.subdomain); - }; - - return { switchWorkspace }; -}; diff --git a/packages/twenty-front/src/modules/workspace-member/graphql/mutations/addUserToWorkspace.ts b/packages/twenty-front/src/modules/workspace-member/graphql/mutations/addUserToWorkspace.ts deleted file mode 100644 index a57f07931..000000000 --- a/packages/twenty-front/src/modules/workspace-member/graphql/mutations/addUserToWorkspace.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { gql } from '@apollo/client'; - -export const ADD_USER_TO_WORKSPACE = gql` - mutation AddUserToWorkspace($inviteHash: String!) { - addUserToWorkspace(inviteHash: $inviteHash) { - id - } - } -`; diff --git a/packages/twenty-front/src/modules/workspace-member/graphql/mutations/addUserToWorkspaceByInviteToken.ts b/packages/twenty-front/src/modules/workspace-member/graphql/mutations/addUserToWorkspaceByInviteToken.ts deleted file mode 100644 index 4850c1059..000000000 --- a/packages/twenty-front/src/modules/workspace-member/graphql/mutations/addUserToWorkspaceByInviteToken.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { gql } from '@apollo/client'; - -export const ADD_USER_TO_WORKSPACE_BY_INVITE_TOKEN = gql` - mutation AddUserToWorkspaceByInviteToken($inviteToken: String!) { - addUserToWorkspaceByInviteToken(inviteToken: $inviteToken) { - id - } - } -`; diff --git a/packages/twenty-front/src/modules/workspace/graphql/queries/getWorkspaceFromInviteHash.ts b/packages/twenty-front/src/modules/workspace/graphql/queries/getWorkspaceFromInviteHash.ts index b18a9d2c6..5b7925f63 100644 --- a/packages/twenty-front/src/modules/workspace/graphql/queries/getWorkspaceFromInviteHash.ts +++ b/packages/twenty-front/src/modules/workspace/graphql/queries/getWorkspaceFromInviteHash.ts @@ -7,6 +7,7 @@ export const GET_WORKSPACE_FROM_INVITE_HASH = gql` displayName logo allowImpersonation + subdomain } } `; diff --git a/packages/twenty-front/src/pages/auth/Invite.tsx b/packages/twenty-front/src/pages/auth/Invite.tsx index b0a9ce14e..86a449b96 100644 --- a/packages/twenty-front/src/pages/auth/Invite.tsx +++ b/packages/twenty-front/src/pages/auth/Invite.tsx @@ -1,104 +1,27 @@ import { Logo } from '@/auth/components/Logo'; import { Title } from '@/auth/components/Title'; -import { FooterNote } from '@/auth/sign-in-up/components/FooterNote'; import { SignInUpWorkspaceScopeForm } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeForm'; -import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm'; import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching'; -import styled from '@emotion/styled'; import { useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; -import { AnimatedEaseIn, Loader, MainButton } from 'twenty-ui'; -import { - useAddUserToWorkspaceByInviteTokenMutation, - useAddUserToWorkspaceMutation, -} from '~/generated/graphql'; -import { isDefined } from '~/utils/isDefined'; -import { currentUserState } from '@/auth/states/currentUserState'; +import { AnimatedEaseIn } from 'twenty-ui'; + import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect'; -const StyledContentContainer = styled.div` - margin-bottom: ${({ theme }) => theme.spacing(8)}; - margin-top: ${({ theme }) => theme.spacing(4)}; -`; - export const Invite = () => { - const { workspace: workspaceFromInviteHash, workspaceInviteHash } = - useWorkspaceFromInviteHash(); - - const { form } = useSignInUpForm(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const currentUser = useRecoilValue(currentUserState); - const [addUserToWorkspace] = useAddUserToWorkspaceMutation(); - const [addUserToWorkspaceByInviteToken] = - useAddUserToWorkspaceByInviteTokenMutation(); - const { switchWorkspace } = useWorkspaceSwitching(); - const [searchParams] = useSearchParams(); - const workspaceInviteToken = searchParams.get('inviteToken'); + const { workspace: workspaceFromInviteHash } = useWorkspaceFromInviteHash(); const title = useMemo(() => { return `Join ${workspaceFromInviteHash?.displayName ?? ''} team`; }, [workspaceFromInviteHash?.displayName]); - const handleUserJoinWorkspace = async () => { - if (isDefined(workspaceInviteToken) && isDefined(workspaceFromInviteHash)) { - await addUserToWorkspaceByInviteToken({ - variables: { - inviteToken: workspaceInviteToken, - }, - }); - } else if ( - isDefined(workspaceInviteHash) && - isDefined(workspaceFromInviteHash) - ) { - await addUserToWorkspace({ - variables: { - inviteHash: workspaceInviteHash, - }, - }); - } else { - return; - } - - await switchWorkspace(workspaceFromInviteHash.id); - }; - - if ( - !isDefined(workspaceFromInviteHash) || - (isDefined(workspaceFromInviteHash) && - isDefined(currentWorkspace) && - workspaceFromInviteHash.id === currentWorkspace.id) - ) { - return <>; - } - return ( <> {title} - {isDefined(currentUser) ? ( - <> - - form.formState.isSubmitting && } - fullWidth - /> - - - - ) : ( - <> - - - - )} + + ); }; diff --git a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx index 0a9f9eed7..bdc719ac1 100644 --- a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx @@ -27,6 +27,7 @@ import { } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; +import { useRedirect } from '@/domain-manager/hooks/useRedirect'; type SwitchInfo = { newInterval: SubscriptionInterval; @@ -38,6 +39,8 @@ type SwitchInfo = { export const SettingsBilling = () => { const { t } = useLingui(); + const { redirect } = useRedirect(); + const MONTHLY_SWITCH_INFO: SwitchInfo = { newInterval: SubscriptionInterval.Year, to: t`to yearly`, @@ -89,7 +92,7 @@ export const SettingsBilling = () => { const openBillingPortal = () => { if (isDefined(data) && isDefined(data.billingPortalSession.url)) { - window.location.replace(data.billingPortalSession.url); + redirect(data.billingPortalSession.url); } }; diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts index 75657d81b..74eb26af2 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts @@ -17,7 +17,6 @@ import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/micr // import { OAuthService } from 'src/engine/core-modules/auth/services/oauth.service'; import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service'; import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service'; -import { SwitchWorkspaceService } from 'src/engine/core-modules/auth/services/switch-workspace.service'; import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy'; import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service'; @@ -103,7 +102,6 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy'; RefreshTokenService, LoginTokenService, ResetPasswordService, - SwitchWorkspaceService, TransientTokenService, ApiKeyService, SocialSsoService, diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.spec.ts index 5cd11f2ae..de7f682ca 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.spec.ts @@ -16,7 +16,6 @@ import { ApiKeyService } from './services/api-key.service'; import { AuthService } from './services/auth.service'; // import { OAuthService } from './services/oauth.service'; import { ResetPasswordService } from './services/reset-password.service'; -import { SwitchWorkspaceService } from './services/switch-workspace.service'; import { EmailVerificationTokenService } from './token/services/email-verification-token.service'; import { LoginTokenService } from './token/services/login-token.service'; import { RenewTokenService } from './token/services/renew-token.service'; @@ -74,10 +73,6 @@ describe('AuthResolver', () => { provide: LoginTokenService, useValue: {}, }, - { - provide: SwitchWorkspaceService, - useValue: {}, - }, { provide: TransientTokenService, useValue: {}, diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts index 0cf3190a7..952df1567 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts @@ -25,9 +25,7 @@ import { import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; import { GetLoginTokenFromEmailVerificationTokenInput } from 'src/engine/core-modules/auth/dto/get-login-token-from-email-verification-token.input'; import { SignUpOutput } from 'src/engine/core-modules/auth/dto/sign-up.output'; -import { SwitchWorkspaceInput } from 'src/engine/core-modules/auth/dto/switch-workspace.input'; import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service'; -import { SwitchWorkspaceService } from 'src/engine/core-modules/auth/services/switch-workspace.service'; import { EmailVerificationTokenService } from 'src/engine/core-modules/auth/token/services/email-verification-token.service'; import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service'; import { RenewTokenService } from 'src/engine/core-modules/auth/token/services/renew-token.service'; @@ -38,7 +36,6 @@ import { EmailVerificationService } from 'src/engine/core-modules/email-verifica import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { UserService } from 'src/engine/core-modules/user/services/user.service'; import { User } from 'src/engine/core-modules/user/user.entity'; -import { PublicWorkspaceDataOutput } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; @@ -70,7 +67,6 @@ export class AuthResolver { private apiKeyService: ApiKeyService, private resetPasswordService: ResetPasswordService, private loginTokenService: LoginTokenService, - private switchWorkspaceService: SwitchWorkspaceService, private transientTokenService: TransientTokenService, private emailVerificationService: EmailVerificationService, // private oauthService: OAuthService, @@ -307,18 +303,6 @@ export class AuthResolver { ); } - @Mutation(() => PublicWorkspaceDataOutput) - @UseGuards(WorkspaceAuthGuard, UserAuthGuard) - async switchWorkspace( - @AuthUser() user: User, - @Args() args: SwitchWorkspaceInput, - ): Promise { - return await this.switchWorkspaceService.switchWorkspace( - user, - args.workspaceId, - ); - } - @Mutation(() => AuthTokens) async renewToken(@Args() args: AppTokenInput): Promise { const tokens = await this.renewTokenService.generateTokensFromRefreshToken( diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.util.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.util.ts index 275eeb7e5..8e829cb6a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.util.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.util.ts @@ -12,9 +12,7 @@ export const PASSWORD_REGEX = /^.{8,}$/; const saltRounds = 10; export const hashPassword = async (password: string) => { - const hash = await bcrypt.hash(password, saltRounds); - - return hash; + return await bcrypt.hash(password, saltRounds); }; export const compareHash = async (password: string, passwordHash: string) => { diff --git a/packages/twenty-server/src/engine/core-modules/auth/dto/switch-workspace.input.ts b/packages/twenty-server/src/engine/core-modules/auth/dto/switch-workspace.input.ts deleted file mode 100644 index 4024deb7f..000000000 --- a/packages/twenty-server/src/engine/core-modules/auth/dto/switch-workspace.input.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ArgsType, Field } from '@nestjs/graphql'; - -import { IsNotEmpty, IsString } from 'class-validator'; - -@ArgsType() -export class SwitchWorkspaceInput { - @Field(() => String) - @IsNotEmpty() - @IsString() - workspaceId: string; -} 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 e0237027a..5d62e6bf7 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 @@ -137,10 +137,7 @@ export class SignInUpService { password: string; passwordHash: string; }) { - const isValid = await compareHash( - await this.generateHash(password), - passwordHash, - ); + const isValid = await compareHash(password, passwordHash); if (!isValid) { throw new AuthException( diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.spec.ts deleted file mode 100644 index c6fa11b5f..000000000 --- a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; - -import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; -import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; -import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { UserService } from 'src/engine/core-modules/user/services/user.service'; -import { User } from 'src/engine/core-modules/user/user.entity'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; - -import { SwitchWorkspaceService } from './switch-workspace.service'; - -describe('SwitchWorkspaceService', () => { - let service: SwitchWorkspaceService; - let userRepository: Repository; - let workspaceRepository: Repository; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - SwitchWorkspaceService, - { - provide: getRepositoryToken(User, 'core'), - useClass: Repository, - }, - { - provide: getRepositoryToken(Workspace, 'core'), - useClass: Repository, - }, - { - provide: AccessTokenService, - useValue: { - generateAccessToken: jest.fn(), - }, - }, - { - provide: RefreshTokenService, - useValue: { - generateRefreshToken: jest.fn(), - }, - }, - { - provide: EnvironmentService, - useValue: { - get: jest.fn(), - }, - }, - { - provide: UserService, - useValue: {}, - }, - ], - }).compile(); - - service = module.get(SwitchWorkspaceService); - userRepository = module.get>( - getRepositoryToken(User, 'core'), - ); - workspaceRepository = module.get>( - getRepositoryToken(Workspace, 'core'), - ); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe('switchWorkspace', () => { - it('should throw an error if user does not exist', async () => { - jest.spyOn(userRepository, 'findBy').mockResolvedValue([]); - jest.spyOn(workspaceRepository, 'findOne').mockResolvedValue(null); - - await expect( - service.switchWorkspace( - { id: 'non-existent-user' } as User, - 'workspace-id', - ), - ).rejects.toThrow(AuthException); - }); - - it('should throw an error if workspace does not exist', async () => { - jest - .spyOn(userRepository, 'findBy') - .mockResolvedValue([{ id: 'user-id' } as User]); - jest.spyOn(workspaceRepository, 'findOne').mockResolvedValue(null); - - await expect( - service.switchWorkspace( - { id: 'user-id' } as User, - 'non-existent-workspace', - ), - ).rejects.toThrow(AuthException); - }); - - it('should throw an error if user does not belong to workspace', async () => { - const mockUser = { id: 'user-id' }; - const mockWorkspace = { - id: 'workspace-id', - workspaceUsers: [{ userId: 'other-user-id' }], - }; - - jest - .spyOn(userRepository, 'findBy') - .mockResolvedValue([mockUser as User]); - jest - .spyOn(workspaceRepository, 'findOne') - .mockResolvedValue(mockWorkspace as any); - - await expect( - service.switchWorkspace(mockUser as User, 'workspace-id'), - ).rejects.toThrow(AuthException); - }); - - it('should return SSO auth info if workspace has SSO providers', async () => { - const mockUser = { id: 'user-id' }; - const mockWorkspace = { - id: 'workspace-id', - workspaceUsers: [{ userId: 'user-id' }], - logo: 'logo', - displayName: 'displayName', - isGoogleAuthEnabled: true, - isPasswordAuthEnabled: true, - isMicrosoftAuthEnabled: false, - workspaceSSOIdentityProviders: [ - { - id: 'sso-id', - }, - ], - }; - - jest - .spyOn(userRepository, 'findBy') - .mockResolvedValue([mockUser as User]); - jest.spyOn(userRepository, 'save').mockResolvedValue(mockUser as User); - jest - .spyOn(workspaceRepository, 'findOne') - .mockResolvedValue(mockWorkspace as any); - - const result = await service.switchWorkspace( - mockUser as User, - 'workspace-id', - ); - - expect(result).toEqual({ - id: mockWorkspace.id, - logo: expect.any(String), - displayName: expect.any(String), - authProviders: expect.any(Object), - }); - }); - - it('should return workspace info if workspace does not have SSO providers', async () => { - const mockUser = { id: 'user-id' }; - const mockWorkspace = { - id: 'workspace-id', - workspaceUsers: [{ userId: 'user-id' }], - workspaceSSOIdentityProviders: [], - logo: 'logo', - displayName: 'displayName', - }; - - jest - .spyOn(userRepository, 'findBy') - .mockResolvedValue([mockUser as User]); - jest - .spyOn(workspaceRepository, 'findOne') - .mockResolvedValue(mockWorkspace as any); - jest.spyOn(userRepository, 'save').mockResolvedValue({} as User); - - const result = await service.switchWorkspace( - mockUser as User, - 'workspace-id', - ); - - expect(result).toEqual({ - id: mockWorkspace.id, - logo: expect.any(String), - displayName: expect.any(String), - authProviders: expect.any(Object), - }); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.ts deleted file mode 100644 index 266ffb461..000000000 --- a/packages/twenty-server/src/engine/core-modules/auth/services/switch-workspace.service.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; - -import { - AuthException, - AuthExceptionCode, -} from 'src/engine/core-modules/auth/auth.exception'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { User } from 'src/engine/core-modules/user/user.entity'; -import { AuthProviders } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output'; -import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; - -@Injectable() -export class SwitchWorkspaceService { - constructor( - @InjectRepository(Workspace, 'core') - private readonly workspaceRepository: Repository, - private readonly environmentService: EnvironmentService, - ) {} - - async switchWorkspace(user: User, workspaceId: string) { - const workspace = await this.workspaceRepository.findOne({ - where: { id: workspaceId }, - relations: ['workspaceUsers', 'workspaceSSOIdentityProviders'], - }); - - workspaceValidator.assertIsDefinedOrThrow( - workspace, - new AuthException('Workspace not found', AuthExceptionCode.INVALID_INPUT), - ); - - if ( - !workspace.workspaceUsers - .map((userWorkspace) => userWorkspace.userId) - .includes(user.id) - ) { - throw new AuthException( - 'user does not belong to workspace', - AuthExceptionCode.FORBIDDEN_EXCEPTION, - ); - } - - const systemEnabledProviders: AuthProviders = { - google: this.environmentService.get('AUTH_GOOGLE_ENABLED'), - magicLink: false, - password: this.environmentService.get('AUTH_PASSWORD_ENABLED'), - microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'), - sso: [], - }; - - return { - id: workspace.id, - subdomain: workspace.subdomain, - logo: workspace.logo, - displayName: workspace.displayName, - authProviders: getAuthProvidersByWorkspace({ - workspace, - systemEnabledProviders, - }), - }; - } -} diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts index 338b6e949..4abba0ee6 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.resolver.ts @@ -1,18 +1,14 @@ import { UseGuards } from '@nestjs/common'; -import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { Resolver } from '@nestjs/graphql'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { WorkspaceInviteHashValidInput } from 'src/engine/core-modules/auth/dto/workspace-invite-hash.input'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; -import { User } from 'src/engine/core-modules/user/user.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; -import { WorkspaceInviteTokenInput } from 'src/engine/core-modules/auth/dto/workspace-invite-token.input'; @UseGuards(WorkspaceAuthGuard) @Resolver(() => UserWorkspace) @@ -23,36 +19,4 @@ export class UserWorkspaceResolver { private readonly userWorkspaceService: UserWorkspaceService, private readonly workspaceInvitationService: WorkspaceInvitationService, ) {} - - @Mutation(() => User) - async addUserToWorkspace( - @AuthUser() user: User, - @Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput, - ) { - const workspace = await this.workspaceRepository.findOneBy({ - inviteHash: workspaceInviteHashValidInput.inviteHash, - }); - - if (!workspace) { - return; - } - - await this.workspaceInvitationService.invalidateWorkspaceInvitation( - workspace.id, - user.email, - ); - - return await this.userWorkspaceService.addUserToWorkspace(user, workspace); - } - - @Mutation(() => User) - async addUserToWorkspaceByInviteToken( - @AuthUser() user: User, - @Args() workspaceInviteTokenInput: WorkspaceInviteTokenInput, - ) { - return this.userWorkspaceService.addUserToWorkspaceByInviteToken( - workspaceInviteTokenInput.inviteToken, - user, - ); - } } diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts index 596bab9af..9bc54f7df 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts @@ -124,21 +124,6 @@ export class UserWorkspaceService extends TypeOrmQueryService { return user; } - async addUserToWorkspaceByInviteToken(inviteToken: string, user: User) { - const appToken = - await this.workspaceInvitationService.validatePersonalInvitation({ - workspacePersonalInviteToken: inviteToken, - email: user.email, - }); - - await this.workspaceInvitationService.invalidateWorkspaceInvitation( - appToken.workspace.id, - user.email, - ); - - return await this.addUserToWorkspace(user, appToken.workspace); - } - public async getUserCount(workspaceId: string): Promise { return await this.userWorkspaceRepository.countBy({ workspaceId,