refacto(invite|signin): remove unused code + fix signin on invite page. (#9745)

- Replace `window.location.replace` by `useRedirect` hook.
- Remove unused code: `switchWorkspace, addUserByInviteHash...`
- Refacto `Invite` component.
- Fix signin on invite modal.
This commit is contained in:
Antoine Moreaux
2025-01-21 16:33:31 +01:00
committed by GitHub
parent 2e9a77f702
commit 34afd73923
28 changed files with 67 additions and 715 deletions

View File

@ -170,6 +170,7 @@ export type ClientConfig = {
frontDomain: Scalars['String']['output'];
isEmailVerificationRequired: Scalars['Boolean']['output'];
isMultiWorkspaceEnabled: Scalars['Boolean']['output'];
publicFeatureFlags: Array<PublicFeatureFlag>;
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<Scalars['String']['input']>;
@ -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<Scalars['JSON']['input']>;
};
export type UpdateLabPublicFeatureFlagInput = {
publicFeatureFlag: Scalars['String']['input'];
value: Scalars['Boolean']['input'];
};
export type UpdateObjectPayload = {
description?: InputMaybe<Scalars['String']['input']>;
icon?: InputMaybe<Scalars['String']['input']>;
@ -1713,7 +1720,7 @@ export type WorkflowRun = {
export type WorkflowVersion = {
__typename?: 'WorkflowVersion';
workflowVersionId: Scalars['UUID']['output'];
id: Scalars['UUID']['output'];
};
export type Workspace = {

View File

@ -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<Scalars['String']>;
@ -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<SignU
export type SignUpMutationHookResult = ReturnType<typeof useSignUpMutation>;
export type SignUpMutationResult = Apollo.MutationResult<SignUpMutation>;
export type SignUpMutationOptions = Apollo.BaseMutationOptions<SignUpMutation, SignUpMutationVariables>;
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<SwitchWorkspaceMutation, SwitchWorkspaceMutationVariables>;
/**
* __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<SwitchWorkspaceMutation, SwitchWorkspaceMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SwitchWorkspaceMutation, SwitchWorkspaceMutationVariables>(SwitchWorkspaceDocument, options);
}
export type SwitchWorkspaceMutationHookResult = ReturnType<typeof useSwitchWorkspaceMutation>;
export type SwitchWorkspaceMutationResult = Apollo.MutationResult<SwitchWorkspaceMutation>;
export type SwitchWorkspaceMutationOptions = Apollo.BaseMutationOptions<SwitchWorkspaceMutation, SwitchWorkspaceMutationVariables>;
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<typeof useGetWorkspaceInvitationsQuery>;
export type GetWorkspaceInvitationsLazyQueryHookResult = ReturnType<typeof useGetWorkspaceInvitationsLazyQuery>;
export type GetWorkspaceInvitationsQueryResult = Apollo.QueryResult<GetWorkspaceInvitationsQuery, GetWorkspaceInvitationsQueryVariables>;
export const AddUserToWorkspaceDocument = gql`
mutation AddUserToWorkspace($inviteHash: String!) {
addUserToWorkspace(inviteHash: $inviteHash) {
id
}
}
`;
export type AddUserToWorkspaceMutationFn = Apollo.MutationFunction<AddUserToWorkspaceMutation, AddUserToWorkspaceMutationVariables>;
/**
* __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<AddUserToWorkspaceMutation, AddUserToWorkspaceMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<AddUserToWorkspaceMutation, AddUserToWorkspaceMutationVariables>(AddUserToWorkspaceDocument, options);
}
export type AddUserToWorkspaceMutationHookResult = ReturnType<typeof useAddUserToWorkspaceMutation>;
export type AddUserToWorkspaceMutationResult = Apollo.MutationResult<AddUserToWorkspaceMutation>;
export type AddUserToWorkspaceMutationOptions = Apollo.BaseMutationOptions<AddUserToWorkspaceMutation, AddUserToWorkspaceMutationVariables>;
export const AddUserToWorkspaceByInviteTokenDocument = gql`
mutation AddUserToWorkspaceByInviteToken($inviteToken: String!) {
addUserToWorkspaceByInviteToken(inviteToken: $inviteToken) {
id
}
}
`;
export type AddUserToWorkspaceByInviteTokenMutationFn = Apollo.MutationFunction<AddUserToWorkspaceByInviteTokenMutation, AddUserToWorkspaceByInviteTokenMutationVariables>;
/**
* __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<AddUserToWorkspaceByInviteTokenMutation, AddUserToWorkspaceByInviteTokenMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<AddUserToWorkspaceByInviteTokenMutation, AddUserToWorkspaceByInviteTokenMutationVariables>(AddUserToWorkspaceByInviteTokenDocument, options);
}
export type AddUserToWorkspaceByInviteTokenMutationHookResult = ReturnType<typeof useAddUserToWorkspaceByInviteTokenMutation>;
export type AddUserToWorkspaceByInviteTokenMutationResult = Apollo.MutationResult<AddUserToWorkspaceByInviteTokenMutation>;
export type AddUserToWorkspaceByInviteTokenMutationOptions = Apollo.BaseMutationOptions<AddUserToWorkspaceByInviteTokenMutation, AddUserToWorkspaceByInviteTokenMutationVariables>;
export const ActivateWorkspaceDocument = gql`
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
activateWorkspace(data: $input) {
@ -4666,6 +4514,7 @@ export const GetWorkspaceFromInviteHashDocument = gql`
displayName
logo
allowImpersonation
subdomain
}
}
`;

View File

@ -69,7 +69,7 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
setCurrentUser(null);
setCurrentWorkspaceMember(null);
setCurrentWorkspace(null);
setWorkspaces(null);
setWorkspaces([]);
if (
!isMatchingLocation(AppPath.Verify) &&
!isMatchingLocation(AppPath.SignInUp) &&

View File

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

View File

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

View File

@ -5,9 +5,9 @@ import { Workspace } from '~/generated/graphql';
export type Workspaces = Pick<
Workspace,
'id' | 'logo' | 'displayName' | 'subdomain'
>;
>[];
export const workspacesState = createState<Workspaces[] | null>({
export const workspacesState = createState<Workspaces>({
key: 'workspacesState',
defaultValue: [],
});

View File

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

View File

@ -6,13 +6,13 @@ export const useBuildWorkspaceUrl = () => {
const domainConfiguration = useRecoilValue(domainConfigurationState);
const buildWorkspaceUrl = (
subdomain?: string,
subdomain: string,
pathname?: string,
searchParams?: Record<string, string>,
) => {
const url = new URL(window.location.href);
if (isDefined(subdomain) && subdomain.length !== 0) {
if (subdomain.length !== 0) {
url.hostname = `${subdomain}.${domainConfiguration.frontDomain}`;
}

View File

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

View File

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

View File

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

View File

@ -57,8 +57,7 @@ export const NavigationDrawerHeader = ({
isNavigationDrawerExpandedState,
);
const isMultiWorkspace =
isMultiWorkspaceEnabled && workspaces !== null && workspaces.length > 1;
const isMultiWorkspace = isMultiWorkspaceEnabled && workspaces.length > 1;
return (
<StyledContainer>

View File

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

View File

@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const ADD_USER_TO_WORKSPACE = gql`
mutation AddUserToWorkspace($inviteHash: String!) {
addUserToWorkspace(inviteHash: $inviteHash) {
id
}
}
`;

View File

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

View File

@ -7,6 +7,7 @@ export const GET_WORKSPACE_FROM_INVITE_HASH = gql`
displayName
logo
allowImpersonation
subdomain
}
}
`;

View File

@ -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 (
<>
<AnimatedEaseIn>
<Logo secondaryLogo={workspaceFromInviteHash?.logo} />
</AnimatedEaseIn>
<Title animate>{title}</Title>
{isDefined(currentUser) ? (
<>
<StyledContentContainer>
<MainButton
title="Continue"
type="submit"
onClick={handleUserJoinWorkspace}
Icon={() => form.formState.isSubmitting && <Loader />}
fullWidth
/>
</StyledContentContainer>
<FooterNote />
</>
) : (
<>
<SignInUpWorkspaceScopeFormEffect />
<SignInUpWorkspaceScopeForm />
</>
)}
<SignInUpWorkspaceScopeFormEffect />
<SignInUpWorkspaceScopeForm />
</>
);
};

View File

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