From e490bd3ee5a188bfa04b02c949f10a2d1607527d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cosmin=20P=C3=A2rvulescu?= Date: Wed, 22 Nov 2023 16:09:52 +0200 Subject: [PATCH] fix(console): Duplicate invitee addresses (#2769) --- .../app/routes/__layout/groups/$groupID.tsx | 2 +- .../routes/__layout/groups/$groupID/index.tsx | 245 ++++++++++-------- .../inviteIdentityGroupMember.ts | 10 + 3 files changed, 154 insertions(+), 103 deletions(-) diff --git a/apps/console/app/routes/__layout/groups/$groupID.tsx b/apps/console/app/routes/__layout/groups/$groupID.tsx index 97fd4e76c7..ea3967d06e 100644 --- a/apps/console/app/routes/__layout/groups/$groupID.tsx +++ b/apps/console/app/routes/__layout/groups/$groupID.tsx @@ -22,7 +22,7 @@ import { requireJWT } from '~/utilities/session.server' import { GroupModel, GroupRootContextData } from '../groups' import { useEffect, useMemo } from 'react' -type InvitationModel = { +export type InvitationModel = { identifier: string accountType: EmailAccountType | OAuthAccountType | CryptoAccountType invitationURL: string diff --git a/apps/console/app/routes/__layout/groups/$groupID/index.tsx b/apps/console/app/routes/__layout/groups/$groupID/index.tsx index 1765c0ed99..8f6cdfed88 100644 --- a/apps/console/app/routes/__layout/groups/$groupID/index.tsx +++ b/apps/console/app/routes/__layout/groups/$groupID/index.tsx @@ -47,7 +47,7 @@ import { } from '@proofzero/design-system/src/atoms/toast' import { IdentityURN } from '@proofzero/urns/identity' import dangerVector from '~/images/danger.svg' -import { GroupDetailsContextData } from '../$groupID' +import { GroupDetailsContextData, InvitationModel } from '../$groupID' import { PurchaseGroupSeatingModal } from '~/components/Billing/seating' import { LoaderData, @@ -107,11 +107,13 @@ export const ActionCard = ({ } const InviteMemberModal = ({ + invitations, passportURL, groupID, isOpen, handleClose, }: { + invitations: InvitationModel[] passportURL: string groupID: string isOpen: boolean @@ -120,6 +122,7 @@ const InviteMemberModal = ({ const [selectedProvider, setSelectedProvider] = useState( EmailAccountType.Email ) + const [identifier, setIdentifier] = useState('') const inviteLinkFetcher = useFetcher() const closeAndClearFetcher = () => { @@ -128,11 +131,27 @@ const InviteMemberModal = ({ method: 'post', }) + setIdentifier('') + setSelectedProvider(EmailAccountType.Email) + handleClose() } + const [invitationExists, setInvitationExists] = useState(false) + useEffect(() => { + if ( + invitations.find( + (i) => i.identifier === identifier && i.accountType === selectedProvider + ) + ) { + setInvitationExists(true) + } else { + setInvitationExists(false) + } + }, [invitations, selectedProvider, identifier]) + return ( - +
@@ -147,119 +166,140 @@ const InviteMemberModal = ({ className={`bg-white p-2 rounded-lg text-xl cursor-pointer hover:bg-[#F3F4F6]`} onClick={() => { - handleClose() + closeAndClearFetcher() }} >
- {!inviteLinkFetcher.data && ( - -
- - {({ open }) => ( -
- -
- - - {_.upperFirst(selectedProvider)} - -
- - {open ? ( - - ) : ( - - )} -
- - - + +
+ + {({ open }) => ( +
+ - {accountTypes.map((provider) => ( - - classNames( - 'flex flex-row items-center gap-2 hover:bg-gray-100 py-2 px-4 rounded-lg cursor-pointer', - { - 'bg-gray-100': active, - } - ) - } +
+ + - {({ selected }) => ( - <> - - - {_.upperFirst(provider)} - - {selected && ( -
+ + {open ? ( + + ) : ( + + )} +
+ + + + {accountTypes.map((provider) => ( + + classNames( + 'flex flex-row items-center gap-2 hover:bg-gray-100 py-2 px-4 rounded-lg cursor-pointer', + { + 'bg-gray-100': active, + } + ) + } + > + {({ selected }) => ( + <> + - )} - - )} - - ))} - - -
- )} -
+ + {_.upperFirst(provider)} + + {selected && ( +
+ )} + + + { + setIdentifier(e.target.value) + }} + /> +
- -
+ +
- - + {invitationExists && ( + + Invitation for {identifier} already exists + + )} + )} - {inviteLinkFetcher.data && ( + {inviteLinkFetcher.data?.inviteCode && ( <> { setInviteModalOpen(false)} diff --git a/platform/identity/src/jsonrpc/methods/identity-groups/inviteIdentityGroupMember.ts b/platform/identity/src/jsonrpc/methods/identity-groups/inviteIdentityGroupMember.ts index 0e3b9e4d15..19ba7823d0 100644 --- a/platform/identity/src/jsonrpc/methods/identity-groups/inviteIdentityGroupMember.ts +++ b/platform/identity/src/jsonrpc/methods/identity-groups/inviteIdentityGroupMember.ts @@ -84,6 +84,16 @@ export const inviteIdentityGroupMember = async ({ const invitations = await node.class.getInvitations() const invitationCount = invitations.length + if ( + invitations.find( + (inv) => inv.identifier === identifier && inv.accountType === accountType + ) + ) { + throw new BadRequestError({ + message: 'Invitation already exists', + }) + } + const seats = await node.class.getSeats() const { edges: groupMembershipEdges } = await caller.edges.getEdges({