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:
@ -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 = {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -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) &&
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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: [],
|
||||
});
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
|
||||
@ -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}`;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -57,8 +57,7 @@ export const NavigationDrawerHeader = ({
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
const isMultiWorkspace =
|
||||
isMultiWorkspaceEnabled && workspaces !== null && workspaces.length > 1;
|
||||
const isMultiWorkspace = isMultiWorkspaceEnabled && workspaces.length > 1;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const ADD_USER_TO_WORKSPACE = gql`
|
||||
mutation AddUserToWorkspace($inviteHash: String!) {
|
||||
addUserToWorkspace(inviteHash: $inviteHash) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -7,6 +7,7 @@ export const GET_WORKSPACE_FROM_INVITE_HASH = gql`
|
||||
displayName
|
||||
logo
|
||||
allowImpersonation
|
||||
subdomain
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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: {},
|
||||
|
||||
@ -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<PublicWorkspaceDataOutput> {
|
||||
return await this.switchWorkspaceService.switchWorkspace(
|
||||
user,
|
||||
args.workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => AuthTokens)
|
||||
async renewToken(@Args() args: AppTokenInput): Promise<AuthTokens> {
|
||||
const tokens = await this.renewTokenService.generateTokensFromRefreshToken(
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -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<User>;
|
||||
let workspaceRepository: Repository<Workspace>;
|
||||
|
||||
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>(SwitchWorkspaceService);
|
||||
userRepository = module.get<Repository<User>>(
|
||||
getRepositoryToken(User, 'core'),
|
||||
);
|
||||
workspaceRepository = module.get<Repository<Workspace>>(
|
||||
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),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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<Workspace>,
|
||||
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,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,21 +124,6 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
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<number | undefined> {
|
||||
return await this.userWorkspaceRepository.countBy({
|
||||
workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user