4689 multi workspace i should be able to accept an invite if im already logged in (#5454)
- split signInUp to separate Invitation from signInUp - update redirection logic - add a resolver for userWorkspace - add a mutation to add a user to a workspace - authorize /invite/hash while loggedIn - add a button to join a workspace ### Base functionnality https://github.com/twentyhq/twenty/assets/29927851/a1075a4e-a2af-4184-aa3e-e163711277a1 ### Error handling https://github.com/twentyhq/twenty/assets/29927851/1bdd78ce-933a-4860-a87a-3f1f7bda389e
This commit is contained in:
@ -38,6 +38,7 @@ import { Authorize } from '~/pages/auth/Authorize';
|
|||||||
import { ChooseYourPlan } from '~/pages/auth/ChooseYourPlan';
|
import { ChooseYourPlan } from '~/pages/auth/ChooseYourPlan';
|
||||||
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
||||||
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
|
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
|
||||||
|
import { Invite } from '~/pages/auth/Invite';
|
||||||
import { PasswordReset } from '~/pages/auth/PasswordReset';
|
import { PasswordReset } from '~/pages/auth/PasswordReset';
|
||||||
import { PaymentSuccess } from '~/pages/auth/PaymentSuccess';
|
import { PaymentSuccess } from '~/pages/auth/PaymentSuccess';
|
||||||
import { SignInUp } from '~/pages/auth/SignInUp';
|
import { SignInUp } from '~/pages/auth/SignInUp';
|
||||||
@ -128,7 +129,7 @@ const createRouter = (isBillingEnabled?: boolean) =>
|
|||||||
<Route element={<DefaultLayout />}>
|
<Route element={<DefaultLayout />}>
|
||||||
<Route path={AppPath.Verify} element={<VerifyEffect />} />
|
<Route path={AppPath.Verify} element={<VerifyEffect />} />
|
||||||
<Route path={AppPath.SignInUp} element={<SignInUp />} />
|
<Route path={AppPath.SignInUp} element={<SignInUp />} />
|
||||||
<Route path={AppPath.Invite} element={<SignInUp />} />
|
<Route path={AppPath.Invite} element={<Invite />} />
|
||||||
<Route path={AppPath.ResetPassword} element={<PasswordReset />} />
|
<Route path={AppPath.ResetPassword} element={<PasswordReset />} />
|
||||||
<Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} />
|
<Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} />
|
||||||
<Route path={AppPath.CreateProfile} element={<CreateProfile />} />
|
<Route path={AppPath.CreateProfile} element={<CreateProfile />} />
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { matchPath, useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { IconCheckbox } from 'twenty-ui';
|
import { IconCheckbox } from 'twenty-ui';
|
||||||
|
|
||||||
@ -20,10 +20,8 @@ import { SettingsPath } from '@/types/SettingsPath';
|
|||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useGetWorkspaceFromInviteHashLazyQuery } from '~/generated/graphql';
|
import { useGetWorkspaceFromInviteHashLazyQuery } from '~/generated/graphql';
|
||||||
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|
||||||
|
|
||||||
import { useIsMatchingLocation } from '../hooks/useIsMatchingLocation';
|
|
||||||
|
|
||||||
// TODO: break down into smaller functions and / or hooks
|
// TODO: break down into smaller functions and / or hooks
|
||||||
export const PageChangeEffect = () => {
|
export const PageChangeEffect = () => {
|
||||||
@ -70,13 +68,6 @@ export const PageChangeEffect = () => {
|
|||||||
isMatchingLocation(AppPath.PlanRequired) ||
|
isMatchingLocation(AppPath.PlanRequired) ||
|
||||||
isMatchingLocation(AppPath.PlanRequiredSuccess);
|
isMatchingLocation(AppPath.PlanRequiredSuccess);
|
||||||
|
|
||||||
const navigateToSignUp = () => {
|
|
||||||
enqueueSnackBar('workspace does not exist', {
|
|
||||||
variant: 'error',
|
|
||||||
});
|
|
||||||
navigate(AppPath.SignInUp);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
onboardingStatus === OnboardingStatus.OngoingUserCreation &&
|
onboardingStatus === OnboardingStatus.OngoingUserCreation &&
|
||||||
!isMatchingOngoingUserCreationRoute &&
|
!isMatchingOngoingUserCreationRoute &&
|
||||||
@ -115,7 +106,8 @@ export const PageChangeEffect = () => {
|
|||||||
navigate(AppPath.CreateProfile);
|
navigate(AppPath.CreateProfile);
|
||||||
} else if (
|
} else if (
|
||||||
onboardingStatus === OnboardingStatus.Completed &&
|
onboardingStatus === OnboardingStatus.Completed &&
|
||||||
isMatchingOnboardingRoute
|
isMatchingOnboardingRoute &&
|
||||||
|
!isMatchingLocation(AppPath.Invite)
|
||||||
) {
|
) {
|
||||||
navigate(AppPath.Index);
|
navigate(AppPath.Index);
|
||||||
} else if (
|
} else if (
|
||||||
@ -124,24 +116,6 @@ export const PageChangeEffect = () => {
|
|||||||
!isMatchingLocation(AppPath.PlanRequired)
|
!isMatchingLocation(AppPath.PlanRequired)
|
||||||
) {
|
) {
|
||||||
navigate(AppPath.Index);
|
navigate(AppPath.Index);
|
||||||
} else if (isMatchingLocation(AppPath.Invite)) {
|
|
||||||
const inviteHash =
|
|
||||||
matchPath({ path: '/invite/:workspaceInviteHash' }, location.pathname)
|
|
||||||
?.params.workspaceInviteHash || '';
|
|
||||||
|
|
||||||
workspaceFromInviteHashQuery({
|
|
||||||
variables: {
|
|
||||||
inviteHash,
|
|
||||||
},
|
|
||||||
onCompleted: (data) => {
|
|
||||||
if (isUndefinedOrNull(data.findWorkspaceFromInviteHash)) {
|
|
||||||
navigateToSignUp();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: (_) => {
|
|
||||||
navigateToSignUp();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
enqueueSnackBar,
|
enqueueSnackBar,
|
||||||
|
|||||||
@ -260,6 +260,7 @@ export type LoginToken = {
|
|||||||
export type Mutation = {
|
export type Mutation = {
|
||||||
__typename?: 'Mutation';
|
__typename?: 'Mutation';
|
||||||
activateWorkspace: Workspace;
|
activateWorkspace: Workspace;
|
||||||
|
addUserToWorkspace: User;
|
||||||
authorizeApp: AuthorizeApp;
|
authorizeApp: AuthorizeApp;
|
||||||
challenge: LoginToken;
|
challenge: LoginToken;
|
||||||
checkoutSession: SessionEntity;
|
checkoutSession: SessionEntity;
|
||||||
@ -294,6 +295,11 @@ export type MutationActivateWorkspaceArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationAddUserToWorkspaceArgs = {
|
||||||
|
inviteHash: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationAuthorizeAppArgs = {
|
export type MutationAuthorizeAppArgs = {
|
||||||
clientId: Scalars['String'];
|
clientId: Scalars['String'];
|
||||||
codeChallenge?: InputMaybe<Scalars['String']>;
|
codeChallenge?: InputMaybe<Scalars['String']>;
|
||||||
@ -539,6 +545,7 @@ export type RelationConnection = {
|
|||||||
export type RelationDefinition = {
|
export type RelationDefinition = {
|
||||||
__typename?: 'RelationDefinition';
|
__typename?: 'RelationDefinition';
|
||||||
direction: RelationDefinitionType;
|
direction: RelationDefinitionType;
|
||||||
|
relationId: Scalars['UUID'];
|
||||||
sourceFieldMetadata: Field;
|
sourceFieldMetadata: Field;
|
||||||
sourceObjectMetadata: Object;
|
sourceObjectMetadata: Object;
|
||||||
targetFieldMetadata: Field;
|
targetFieldMetadata: Field;
|
||||||
@ -578,6 +585,7 @@ export type RemoteTable = {
|
|||||||
id?: Maybe<Scalars['UUID']>;
|
id?: Maybe<Scalars['UUID']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
schema?: Maybe<Scalars['String']>;
|
schema?: Maybe<Scalars['String']>;
|
||||||
|
schemaPendingUpdates?: Maybe<Array<TableUpdate>>;
|
||||||
status: RemoteTableStatus;
|
status: RemoteTableStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -617,6 +625,14 @@ export type Support = {
|
|||||||
supportFrontChatId?: Maybe<Scalars['String']>;
|
supportFrontChatId?: Maybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Schema update on a table */
|
||||||
|
export enum TableUpdate {
|
||||||
|
ColumnsAdded = 'COLUMNS_ADDED',
|
||||||
|
ColumnsDeleted = 'COLUMNS_DELETED',
|
||||||
|
ColumnsTypeChanged = 'COLUMNS_TYPE_CHANGED',
|
||||||
|
TableDeleted = 'TABLE_DELETED'
|
||||||
|
}
|
||||||
|
|
||||||
export type Telemetry = {
|
export type Telemetry = {
|
||||||
__typename?: 'Telemetry';
|
__typename?: 'Telemetry';
|
||||||
anonymizationEnabled: Scalars['Boolean'];
|
anonymizationEnabled: Scalars['Boolean'];
|
||||||
@ -1191,6 +1207,13 @@ export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
|||||||
|
|
||||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null } | null }> } };
|
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null } | null }> } };
|
||||||
|
|
||||||
|
export type AddUserToWorkspaceMutationVariables = Exact<{
|
||||||
|
inviteHash: Scalars['String'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type AddUserToWorkspaceMutation = { __typename?: 'Mutation', addUserToWorkspace: { __typename?: 'User', id: any } };
|
||||||
|
|
||||||
export type ActivateWorkspaceMutationVariables = Exact<{
|
export type ActivateWorkspaceMutationVariables = Exact<{
|
||||||
input: ActivateWorkspaceInput;
|
input: ActivateWorkspaceInput;
|
||||||
}>;
|
}>;
|
||||||
@ -2456,6 +2479,39 @@ export function useGetCurrentUserLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt
|
|||||||
export type GetCurrentUserQueryHookResult = ReturnType<typeof useGetCurrentUserQuery>;
|
export type GetCurrentUserQueryHookResult = ReturnType<typeof useGetCurrentUserQuery>;
|
||||||
export type GetCurrentUserLazyQueryHookResult = ReturnType<typeof useGetCurrentUserLazyQuery>;
|
export type GetCurrentUserLazyQueryHookResult = ReturnType<typeof useGetCurrentUserLazyQuery>;
|
||||||
export type GetCurrentUserQueryResult = Apollo.QueryResult<GetCurrentUserQuery, GetCurrentUserQueryVariables>;
|
export type GetCurrentUserQueryResult = Apollo.QueryResult<GetCurrentUserQuery, GetCurrentUserQueryVariables>;
|
||||||
|
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 ActivateWorkspaceDocument = gql`
|
export const ActivateWorkspaceDocument = gql`
|
||||||
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
|
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
|
||||||
activateWorkspace(data: $input) {
|
activateWorkspace(data: $input) {
|
||||||
|
|||||||
@ -6,11 +6,17 @@ import { motion } from 'framer-motion';
|
|||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { IconGoogle, IconMicrosoft } from 'twenty-ui';
|
import { IconGoogle, IconMicrosoft } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { FooterNote } from '@/auth/sign-in-up/components/FooterNote';
|
||||||
|
import { HorizontalSeparator } from '@/auth/sign-in-up/components/HorizontalSeparator';
|
||||||
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||||
|
import {
|
||||||
|
SignInUpMode,
|
||||||
|
SignInUpStep,
|
||||||
|
useSignInUp,
|
||||||
|
} from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||||
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
||||||
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
|
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
|
||||||
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
|
|
||||||
import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState';
|
import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState';
|
||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
||||||
@ -18,16 +24,8 @@ import { Loader } from '@/ui/feedback/loader/components/Loader';
|
|||||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { ActionLink } from '@/ui/navigation/link/components/ActionLink';
|
import { ActionLink } from '@/ui/navigation/link/components/ActionLink';
|
||||||
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { Logo } from '../../components/Logo';
|
|
||||||
import { Title } from '../../components/Title';
|
|
||||||
import { SignInUpMode, SignInUpStep, useSignInUp } from '../hooks/useSignInUp';
|
|
||||||
|
|
||||||
import { FooterNote } from './FooterNote';
|
|
||||||
import { HorizontalSeparator } from './HorizontalSeparator';
|
|
||||||
|
|
||||||
const StyledContentContainer = styled.div`
|
const StyledContentContainer = styled.div`
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||||
@ -55,14 +53,12 @@ export const SignInUpForm = () => {
|
|||||||
);
|
);
|
||||||
const [authProviders] = useRecoilState(authProvidersState);
|
const [authProviders] = useRecoilState(authProvidersState);
|
||||||
const [showErrors, setShowErrors] = useState(false);
|
const [showErrors, setShowErrors] = useState(false);
|
||||||
const { handleResetPassword } = useHandleResetPassword();
|
|
||||||
const workspace = useWorkspaceFromInviteHash();
|
|
||||||
const { signInWithGoogle } = useSignInWithGoogle();
|
const { signInWithGoogle } = useSignInWithGoogle();
|
||||||
const { signInWithMicrosoft } = useSignInWithMicrosoft();
|
const { signInWithMicrosoft } = useSignInWithMicrosoft();
|
||||||
const { form } = useSignInUpForm();
|
const { form } = useSignInUpForm();
|
||||||
|
const { handleResetPassword } = useHandleResetPassword();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isInviteMode,
|
|
||||||
signInUpStep,
|
signInUpStep,
|
||||||
signInUpMode,
|
signInUpMode,
|
||||||
continueWithCredentials,
|
continueWithCredentials,
|
||||||
@ -101,23 +97,6 @@ export const SignInUpForm = () => {
|
|||||||
return signInUpMode === SignInUpMode.SignIn ? 'Sign in' : 'Sign up';
|
return signInUpMode === SignInUpMode.SignIn ? 'Sign in' : 'Sign up';
|
||||||
}, [signInUpMode, signInUpStep]);
|
}, [signInUpMode, signInUpStep]);
|
||||||
|
|
||||||
const title = useMemo(() => {
|
|
||||||
if (isInviteMode) {
|
|
||||||
return `Join ${workspace?.displayName ?? ''} team`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
signInUpStep === SignInUpStep.Init ||
|
|
||||||
signInUpStep === SignInUpStep.Email
|
|
||||||
) {
|
|
||||||
return 'Welcome to Twenty';
|
|
||||||
}
|
|
||||||
|
|
||||||
return signInUpMode === SignInUpMode.SignIn
|
|
||||||
? 'Sign in to Twenty'
|
|
||||||
: 'Sign up to Twenty';
|
|
||||||
}, [signInUpMode, workspace?.displayName, isInviteMode, signInUpStep]);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const shouldWaitForCaptchaToken =
|
const shouldWaitForCaptchaToken =
|
||||||
@ -143,10 +122,6 @@ export const SignInUpForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AnimatedEaseIn>
|
|
||||||
<Logo workspaceLogo={workspace?.logo} />
|
|
||||||
</AnimatedEaseIn>
|
|
||||||
<Title animate>{title}</Title>
|
|
||||||
<StyledContentContainer>
|
<StyledContentContainer>
|
||||||
{authProviders.google && (
|
{authProviders.google && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -4,8 +4,13 @@ import { useGetWorkspaceFromInviteHashQuery } from '~/generated/graphql';
|
|||||||
|
|
||||||
export const useWorkspaceFromInviteHash = () => {
|
export const useWorkspaceFromInviteHash = () => {
|
||||||
const workspaceInviteHash = useParams().workspaceInviteHash;
|
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||||
const { data: workspaceFromInviteHash } = useGetWorkspaceFromInviteHashQuery({
|
const { data: workspaceFromInviteHash, loading } =
|
||||||
variables: { inviteHash: workspaceInviteHash || '' },
|
useGetWorkspaceFromInviteHashQuery({
|
||||||
});
|
variables: { inviteHash: workspaceInviteHash || '' },
|
||||||
return workspaceFromInviteHash?.findWorkspaceFromInviteHash;
|
});
|
||||||
|
return {
|
||||||
|
workspace: workspaceFromInviteHash?.findWorkspaceFromInviteHash,
|
||||||
|
workspaceInviteHash,
|
||||||
|
loading,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -80,6 +80,7 @@ export const DefaultLayout = () => {
|
|||||||
OnboardingStatus.OngoingWorkspaceActivation,
|
OnboardingStatus.OngoingWorkspaceActivation,
|
||||||
].includes(onboardingStatus)) ||
|
].includes(onboardingStatus)) ||
|
||||||
isMatchingLocation(AppPath.ResetPassword) ||
|
isMatchingLocation(AppPath.ResetPassword) ||
|
||||||
|
isMatchingLocation(AppPath.Invite) ||
|
||||||
(isMatchingLocation(AppPath.PlanRequired) &&
|
(isMatchingLocation(AppPath.PlanRequired) &&
|
||||||
(OnboardingStatus.CompletedWithoutSubscription ||
|
(OnboardingStatus.CompletedWithoutSubscription ||
|
||||||
OnboardingStatus.Canceled))
|
OnboardingStatus.Canceled))
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { useGenerateJwtMutation } from '~/generated/graphql';
|
import { useGenerateJwtMutation } from '~/generated/graphql';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const useWorkspaceSwitching = () => {
|
export const useWorkspaceSwitching = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||||
const [generateJWT] = useGenerateJwtMutation();
|
const [generateJWT] = useGenerateJwtMutation();
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
@ -30,8 +29,7 @@ export const useWorkspaceSwitching = () => {
|
|||||||
|
|
||||||
const { tokens } = jwt.data.generateJWT;
|
const { tokens } = jwt.data.generateJWT;
|
||||||
setTokenPair(tokens);
|
setTokenPair(tokens);
|
||||||
navigate(`/objects/companies`);
|
window.location.href = AppPath.Index;
|
||||||
window.location.reload();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { switchWorkspace };
|
return { switchWorkspace };
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const ADD_USER_TO_WORKSPACE = gql`
|
||||||
|
mutation AddUserToWorkspace($inviteHash: String!) {
|
||||||
|
addUserToWorkspace(inviteHash: $inviteHash) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
122
packages/twenty-front/src/pages/auth/Invite.tsx
Normal file
122
packages/twenty-front/src/pages/auth/Invite.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { Logo } from '@/auth/components/Logo';
|
||||||
|
import { Title } from '@/auth/components/Title';
|
||||||
|
import { FooterNote } from '@/auth/sign-in-up/components/FooterNote';
|
||||||
|
import { SignInUpForm } from '@/auth/sign-in-up/components/SignInUpForm';
|
||||||
|
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 { AppPath } from '@/types/AppPath';
|
||||||
|
import { Loader } from '@/ui/feedback/loader/components/Loader';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||||
|
import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching';
|
||||||
|
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
|
||||||
|
import { useAddUserToWorkspaceMutation } from '~/generated/graphql';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
const StyledContentContainer = styled.div`
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||||
|
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Invite = () => {
|
||||||
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const {
|
||||||
|
workspace: workspaceFromInviteHash,
|
||||||
|
loading: workspaceFromInviteHashLoading,
|
||||||
|
workspaceInviteHash,
|
||||||
|
} = useWorkspaceFromInviteHash();
|
||||||
|
const { form } = useSignInUpForm();
|
||||||
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
const [addUserToWorkspace] = useAddUserToWorkspaceMutation();
|
||||||
|
const { switchWorkspace } = useWorkspaceSwitching();
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
return `Join ${workspaceFromInviteHash?.displayName ?? ''} team`;
|
||||||
|
}, [workspaceFromInviteHash?.displayName]);
|
||||||
|
|
||||||
|
const handleUserJoinWorkspace = async () => {
|
||||||
|
if (
|
||||||
|
!(isDefined(workspaceInviteHash) && isDefined(workspaceFromInviteHash))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await addUserToWorkspace({
|
||||||
|
variables: {
|
||||||
|
inviteHash: workspaceInviteHash,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await switchWorkspace(workspaceFromInviteHash.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!isDefined(workspaceFromInviteHash) &&
|
||||||
|
!workspaceFromInviteHashLoading
|
||||||
|
) {
|
||||||
|
enqueueSnackBar('workspace does not exist', {
|
||||||
|
variant: 'error',
|
||||||
|
});
|
||||||
|
if (isDefined(currentWorkspace)) {
|
||||||
|
navigate(AppPath.Index);
|
||||||
|
} else {
|
||||||
|
navigate(AppPath.SignInUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isDefined(currentWorkspace) &&
|
||||||
|
currentWorkspace.id === workspaceFromInviteHash?.id
|
||||||
|
) {
|
||||||
|
enqueueSnackBar(
|
||||||
|
`You already belong to ${workspaceFromInviteHash?.displayName} workspace`,
|
||||||
|
{
|
||||||
|
variant: 'info',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
navigate(AppPath.Index);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
navigate,
|
||||||
|
enqueueSnackBar,
|
||||||
|
currentWorkspace,
|
||||||
|
workspaceFromInviteHash,
|
||||||
|
workspaceFromInviteHashLoading,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
!workspaceFromInviteHashLoading && (
|
||||||
|
<>
|
||||||
|
<AnimatedEaseIn>
|
||||||
|
<Logo workspaceLogo={workspaceFromInviteHash?.logo} />
|
||||||
|
</AnimatedEaseIn>
|
||||||
|
<Title animate>{title}</Title>
|
||||||
|
{isDefined(currentWorkspace) && workspaceFromInviteHash ? (
|
||||||
|
<>
|
||||||
|
<StyledContentContainer>
|
||||||
|
<MainButton
|
||||||
|
variant="secondary"
|
||||||
|
title="Continue"
|
||||||
|
type="submit"
|
||||||
|
onClick={handleUserJoinWorkspace}
|
||||||
|
Icon={() => form.formState.isSubmitting && <Loader />}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</StyledContentContainer>
|
||||||
|
<FooterNote>
|
||||||
|
By using Twenty, you agree to the Terms of Service and Privacy
|
||||||
|
Policy.
|
||||||
|
</FooterNote>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<SignInUpForm />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,3 +1,43 @@
|
|||||||
import { SignInUpForm } from '../../modules/auth/sign-in-up/components/SignInUpForm';
|
import { useMemo } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
export const SignInUp = () => <SignInUpForm />;
|
import { Title } from '@/auth/components/Title';
|
||||||
|
import { SignInUpForm } from '@/auth/sign-in-up/components/SignInUpForm';
|
||||||
|
import {
|
||||||
|
SignInUpMode,
|
||||||
|
SignInUpStep,
|
||||||
|
useSignInUp,
|
||||||
|
} from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||||
|
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||||
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const SignInUp = () => {
|
||||||
|
const { form } = useSignInUpForm();
|
||||||
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
|
const { signInUpStep, signInUpMode } = useSignInUp(form);
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
if (
|
||||||
|
signInUpStep === SignInUpStep.Init ||
|
||||||
|
signInUpStep === SignInUpStep.Email
|
||||||
|
) {
|
||||||
|
return 'Welcome to Twenty';
|
||||||
|
}
|
||||||
|
return signInUpMode === SignInUpMode.SignIn
|
||||||
|
? 'Sign in to Twenty'
|
||||||
|
: 'Sign up to Twenty';
|
||||||
|
}, [signInUpMode, signInUpStep]);
|
||||||
|
|
||||||
|
if (isDefined(currentWorkspace)) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title animate>{title}</Title>
|
||||||
|
<SignInUpForm />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import {
|
|||||||
PageDecoratorArgs,
|
PageDecoratorArgs,
|
||||||
} from '~/testing/decorators/PageDecorator';
|
} from '~/testing/decorators/PageDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
|
||||||
|
|
||||||
import { SignInUp } from '../SignInUp';
|
import { SignInUp } from '../SignInUp';
|
||||||
|
|
||||||
@ -24,14 +23,25 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
handlers: [
|
handlers: [
|
||||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: null,
|
||||||
currentUser: mockedOnboardingUsersData[0],
|
errors: [
|
||||||
},
|
{
|
||||||
|
message: 'Unauthorized',
|
||||||
|
extensions: {
|
||||||
|
code: 'UNAUTHENTICATED',
|
||||||
|
response: {
|
||||||
|
statusCode: 401,
|
||||||
|
message: 'Unauthorized',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
graphqlMocks.handlers,
|
graphqlMocks.handlers,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
cookie: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,6 @@ import { EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/email-p
|
|||||||
import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity';
|
import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity';
|
||||||
import { EmailPasswordResetLinkInput } from 'src/engine/core-modules/auth/dto/email-password-reset-link.input';
|
import { EmailPasswordResetLinkInput } from 'src/engine/core-modules/auth/dto/email-password-reset-link.input';
|
||||||
import { GenerateJwtInput } from 'src/engine/core-modules/auth/dto/generate-jwt.input';
|
import { GenerateJwtInput } from 'src/engine/core-modules/auth/dto/generate-jwt.input';
|
||||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
|
||||||
import { AuthorizeApp } from 'src/engine/core-modules/auth/dto/authorize-app.entity';
|
import { AuthorizeApp } from 'src/engine/core-modules/auth/dto/authorize-app.entity';
|
||||||
import { AuthorizeAppInput } from 'src/engine/core-modules/auth/dto/authorize-app.input';
|
import { AuthorizeAppInput } from 'src/engine/core-modules/auth/dto/authorize-app.input';
|
||||||
import { ExchangeAuthCodeInput } from 'src/engine/core-modules/auth/dto/exchange-auth-code.input';
|
import { ExchangeAuthCodeInput } from 'src/engine/core-modules/auth/dto/exchange-auth-code.input';
|
||||||
@ -56,7 +55,6 @@ export class AuthResolver {
|
|||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private userWorkspaceService: UserWorkspaceService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@UseGuards(CaptchaGuard)
|
@UseGuards(CaptchaGuard)
|
||||||
|
|||||||
@ -150,26 +150,10 @@ export class SignInUpService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
const userWorkspaceExists =
|
const updatedUser = await this.userWorkspaceService.addUserToWorkspace(
|
||||||
await this.userWorkspaceService.checkUserWorkspaceExists(
|
existingUser,
|
||||||
existingUser.id,
|
workspace,
|
||||||
workspace.id,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (!userWorkspaceExists) {
|
|
||||||
await this.userWorkspaceService.create(existingUser.id, workspace.id);
|
|
||||||
|
|
||||||
await this.userWorkspaceService.createWorkspaceMember(
|
|
||||||
workspace.id,
|
|
||||||
existingUser,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedUser = await this.userRepository.save({
|
|
||||||
id: existingUser.id,
|
|
||||||
defaultWorkspace: workspace,
|
|
||||||
updatedAt: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return Object.assign(existingUser, updatedUser);
|
return Object.assign(existingUser, updatedUser);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,12 +8,13 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
|
|||||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
NestjsQueryGraphQLModule.forFeature({
|
NestjsQueryGraphQLModule.forFeature({
|
||||||
imports: [
|
imports: [
|
||||||
NestjsQueryTypeOrmModule.forFeature([UserWorkspace], 'core'),
|
NestjsQueryTypeOrmModule.forFeature([User, UserWorkspace], 'core'),
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { UseGuards } from '@nestjs/common';
|
||||||
|
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||||
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
|
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||||
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
import { WorkspaceInviteHashValidInput } from 'src/engine/core-modules/auth/dto/workspace-invite-hash.input';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||||
|
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Resolver(() => UserWorkspace)
|
||||||
|
export class UserWorkspaceResolver {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Workspace, 'core')
|
||||||
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
@InjectRepository(User, 'core')
|
||||||
|
private readonly userRepository: Repository<User>,
|
||||||
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Mutation(() => User)
|
||||||
|
async addUserToWorkspace(
|
||||||
|
@AuthUser() user: User,
|
||||||
|
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
||||||
|
) {
|
||||||
|
const workspace = await this.workspaceRepository.findOneBy({
|
||||||
|
inviteHash: workspaceInviteHashValidInput.inviteHash,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.userWorkspaceService.addUserToWorkspace(user, workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,11 +12,14 @@ import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/work
|
|||||||
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
|
||||||
export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(UserWorkspace, 'core')
|
@InjectRepository(UserWorkspace, 'core')
|
||||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||||
|
@InjectRepository(User, 'core')
|
||||||
|
private readonly userRepository: Repository<User>,
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
private readonly typeORMService: TypeORMService,
|
private readonly typeORMService: TypeORMService,
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
@ -70,6 +73,25 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
|||||||
this.eventEmitter.emit('workspaceMember.created', payload);
|
this.eventEmitter.emit('workspaceMember.created', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addUserToWorkspace(user: User, workspace: Workspace) {
|
||||||
|
const userWorkspaceExists = await this.checkUserWorkspaceExists(
|
||||||
|
user.id,
|
||||||
|
workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!userWorkspaceExists) {
|
||||||
|
await this.create(user.id, workspace.id);
|
||||||
|
|
||||||
|
await this.createWorkspaceMember(workspace.id, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.userRepository.save({
|
||||||
|
id: user.id,
|
||||||
|
defaultWorkspace: workspace,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async getWorkspaceMemberCount(
|
public async getWorkspaceMemberCount(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<number | undefined> {
|
): Promise<number | undefined> {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
|
|||||||
import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener';
|
import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener';
|
||||||
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
import { UserWorkspaceResolver } from 'src/engine/core-modules/user-workspace/user-workspace.resolver';
|
||||||
|
|
||||||
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
||||||
import { Workspace } from './workspace.entity';
|
import { Workspace } from './workspace.entity';
|
||||||
@ -46,6 +47,7 @@ import { WorkspaceService } from './services/workspace.service';
|
|||||||
providers: [
|
providers: [
|
||||||
WorkspaceResolver,
|
WorkspaceResolver,
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
|
UserWorkspaceResolver,
|
||||||
WorkspaceWorkspaceMemberListener,
|
WorkspaceWorkspaceMemberListener,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user