feat(invitation): Improve invitation flow - Milestone 2 (#6804)
From PR: #6626 Resolves #6763 Resolves #6055 Resolves #6782 ## GTK I retain the 'Invite by link' feature to prevent any breaking changes. We could make the invitation by link optional through an admin setting, allowing users to rely solely on personal invitations. ## Todo - [x] Add an expiration date to an invitation - [x] Allow to renew an invitation to postpone the expiration date - [x] Refresh the UI - [x] Add the new personal token in the link sent to new user - [x] Display an error if a user tries to use an expired invitation - [x] Display an error if a user uses another mail than the one in the invitation --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,9 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useEffect } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconMail, IconSend } from 'twenty-ui';
|
||||
import { IconSend } from 'twenty-ui';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
@ -11,12 +11,13 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { sanitizeEmailList } from '@/workspace/utils/sanitizeEmailList';
|
||||
import { useSendInviteLinkMutation } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { useCreateWorkspaceInvitation } from '../../workspace-invitation/hooks/useCreateWorkspaceInvitation';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledLinkContainer = styled.div`
|
||||
@ -69,7 +70,7 @@ type FormInput = {
|
||||
|
||||
export const WorkspaceInviteTeam = () => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const [sendInviteLink] = useSendInviteLinkMutation();
|
||||
const { sendInvitation } = useCreateWorkspaceInvitation();
|
||||
|
||||
const { reset, handleSubmit, control, formState } = useForm<FormInput>({
|
||||
mode: 'onSubmit',
|
||||
@ -79,16 +80,27 @@ export const WorkspaceInviteTeam = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const submit = handleSubmit(async (data) => {
|
||||
const emailsList = sanitizeEmailList(data.emails.split(','));
|
||||
const result = await sendInviteLink({ variables: { emails: emailsList } });
|
||||
if (isDefined(result.errors)) {
|
||||
throw result.errors;
|
||||
const submit = handleSubmit(async ({ emails }) => {
|
||||
const emailsList = sanitizeEmailList(emails.split(','));
|
||||
const { data } = await sendInvitation({ emails: emailsList });
|
||||
if (isDefined(data) && data.sendInvitations.result.length > 0) {
|
||||
enqueueSnackBar(
|
||||
`${data.sendInvitations.result.length} invitations sent`,
|
||||
{
|
||||
variant: SnackBarVariant.Success,
|
||||
duration: 2000,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isDefined(data) && !data.sendInvitations.success) {
|
||||
data.sendInvitations.errors.forEach((error) => {
|
||||
enqueueSnackBar(error, {
|
||||
variant: SnackBarVariant.Error,
|
||||
duration: 5000,
|
||||
});
|
||||
});
|
||||
}
|
||||
enqueueSnackBar('Invite link sent to email addresses', {
|
||||
variant: SnackBarVariant.Success,
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
@ -116,7 +128,6 @@ export const WorkspaceInviteTeam = () => {
|
||||
return (
|
||||
<TextInput
|
||||
placeholder="tim@apple.com, jony.ive@apple.dev"
|
||||
LeftIcon={IconMail}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
error={error?.message}
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui';
|
||||
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.spacing(2)};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(0)};
|
||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledContent = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: ${({ theme }) => theme.spacing(3)};
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const StyledEmailText = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
type WorkspaceMemberCardProps = {
|
||||
workspaceMember: WorkspaceMember;
|
||||
accessory?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const WorkspaceMemberCard = ({
|
||||
workspaceMember,
|
||||
accessory,
|
||||
}: WorkspaceMemberCardProps) => (
|
||||
<StyledContainer>
|
||||
<Avatar
|
||||
avatarUrl={workspaceMember.avatarUrl}
|
||||
placeholderColorSeed={workspaceMember.id}
|
||||
placeholder={workspaceMember.name.firstName || ''}
|
||||
type="squared"
|
||||
size="xl"
|
||||
/>
|
||||
<StyledContent>
|
||||
<OverflowingTextWithTooltip
|
||||
text={
|
||||
workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName
|
||||
}
|
||||
/>
|
||||
<StyledEmailText>{workspaceMember.userEmail}</StyledEmailText>
|
||||
</StyledContent>
|
||||
{accessory}
|
||||
</StyledContainer>
|
||||
);
|
||||
@ -1,9 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const SEND_INVITE_LINK = gql`
|
||||
mutation SendInviteLink($emails: [String!]!) {
|
||||
sendInviteLink(emails: $emails) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user