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:
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,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedOnboardingUsersData } from '~/testing/mock-data/users';
|
||||
|
||||
import { SignInUp } from '../SignInUp';
|
||||
|
||||
@ -24,14 +23,25 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
handlers: [
|
||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
currentUser: mockedOnboardingUsersData[0],
|
||||
},
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message: 'Unauthorized',
|
||||
extensions: {
|
||||
code: 'UNAUTHENTICATED',
|
||||
response: {
|
||||
statusCode: 401,
|
||||
message: 'Unauthorized',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}),
|
||||
graphqlMocks.handlers,
|
||||
],
|
||||
},
|
||||
cookie: '',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user