import './InviteTeamMembers.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Input, Select, Typography } from 'antd'; import inviteUsers from 'api/user/inviteUsers'; import { AxiosError } from 'axios'; import { cloneDeep, debounce, isEmpty } from 'lodash-es'; import { ArrowLeft, ArrowRight, CheckCircle, Plus, TriangleAlert, X, } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { useMutation } from 'react-query'; import { ErrorResponse } from 'types/api'; import { v4 as uuid } from 'uuid'; interface TeamMember { email: string; role: string; name: string; frontendBaseUrl: string; id: string; } interface InviteTeamMembersProps { teamMembers: TeamMember[] | null; setTeamMembers: (teamMembers: TeamMember[]) => void; onNext: () => void; onBack: () => void; } function InviteTeamMembers({ teamMembers, setTeamMembers, onNext, onBack, }: InviteTeamMembersProps): JSX.Element { const [teamMembersToInvite, setTeamMembersToInvite] = useState< TeamMember[] | null >(teamMembers); const [emailValidity, setEmailValidity] = useState>( {}, ); const [hasInvalidEmails, setHasInvalidEmails] = useState(false); const [error, setError] = useState(null); const defaultTeamMember: TeamMember = { email: '', role: 'EDITOR', name: '', frontendBaseUrl: window.location.origin, id: '', }; useEffect(() => { if (isEmpty(teamMembers)) { const teamMember = { ...defaultTeamMember, id: uuid(), }; setTeamMembersToInvite([teamMember]); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [teamMembers]); const handleAddTeamMember = (): void => { const newTeamMember = { ...defaultTeamMember, id: uuid(), }; setTeamMembersToInvite((prev) => [...(prev || []), newTeamMember]); }; const handleRemoveTeamMember = (id: string): void => { setTeamMembersToInvite((prev) => (prev || []).filter((m) => m.id !== id)); }; // Validation function to check all users const validateAllUsers = (): boolean => { let isValid = true; const updatedValidity: Record = {}; teamMembersToInvite?.forEach((member) => { const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(member.email); if (!emailValid || !member.email) { isValid = false; setHasInvalidEmails(true); } updatedValidity[member.id!] = emailValid; }); setEmailValidity(updatedValidity); return isValid; }; const handleError = (error: AxiosError): void => { const errorMessage = error.response?.data as ErrorResponse; setError(errorMessage.error); }; const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation( inviteUsers, { onSuccess: (): void => { onNext(); }, onError: (error): void => { handleError(error as AxiosError); }, }, ); const handleNext = (): void => { if (validateAllUsers()) { setTeamMembers(teamMembersToInvite || []); setError(null); setHasInvalidEmails(false); sendInvites({ users: teamMembersToInvite || [], }); } }; // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedValidateEmail = useCallback( debounce((email: string, memberId: string) => { const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); setEmailValidity((prev) => ({ ...prev, [memberId]: isValid })); }, 500), [], ); const handleEmailChange = ( e: React.ChangeEvent, member: TeamMember, ): void => { const { value } = e.target; const updatedMembers = cloneDeep(teamMembersToInvite || []); const memberToUpdate = updatedMembers.find((m) => m.id === member.id); if (memberToUpdate) { memberToUpdate.email = value; setTeamMembersToInvite(updatedMembers); debouncedValidateEmail(value, member.id!); } }; const handleRoleChange = (role: string, member: TeamMember): void => { const updatedMembers = cloneDeep(teamMembersToInvite || []); const memberToUpdate = updatedMembers.find((m) => m.id === member.id); if (memberToUpdate) { memberToUpdate.role = role; setTeamMembersToInvite(updatedMembers); } }; const handleDoLater = (): void => { onNext(); }; return (
Observability made collaborative The more your team uses SigNoz, the stronger your observability. Share dashboards, collaborate on alerts, and troubleshoot faster together.
Collaborate with your team
Invite your team to the SigNoz workspace
{teamMembersToInvite?.map((member) => (
): void => handleEmailChange(e, member) } addonAfter={ // eslint-disable-next-line no-nested-ternary emailValidity[member.id!] === undefined ? null : emailValidity[ member.id! ] ? ( ) : ( ) } /> {teamMembersToInvite?.length > 1 && (
))}
{hasInvalidEmails && (
Please enter valid emails for all team members
)} {error && (
{error}
)}
); } export default InviteTeamMembers;