From 783f8dea76b99ce72711dbfa383e627f2ba957be Mon Sep 17 00:00:00 2001 From: Mamadou DICKO <63923024+mamadoudicko@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:56:25 +0200 Subject: [PATCH] [ShareableBrain] User email and role inputs form (#608) * feat: add invitation emails form * test(ShareBrain): add tests --- .../components/BrainActions/BrainActions.tsx | 16 +-- .../components/BrainActions/ShareBrain.tsx | 47 --------- .../BrainActions/components/DeleteBrain.tsx | 19 ++++ .../components/ShareBrain/ShareBrain.tsx | 97 +++++++++++++++++++ .../ShareBrain/__tests__/ShareBrain.test.tsx | 50 ++++++++++ .../ShareBrain/components/InvitedUserRow.tsx | 69 +++++++++++++ .../components/ShareBrain/components/index.ts | 1 + .../ShareBrain/hooks/useShareBrain.ts | 83 ++++++++++++++++ .../components/ShareBrain/index.ts | 1 + .../utils/generateBrainAssignation.ts | 9 ++ .../BrainActions/components/index.ts | 2 + .../components/BrainActions/types.ts | 9 ++ frontend/lib/components/ui/Select.tsx | 96 ++++++++++++++++++ frontend/package.json | 1 + frontend/yarn.lock | 49 ++++++++++ 15 files changed, 488 insertions(+), 61 deletions(-) delete mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/ShareBrain.tsx create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/DeleteBrain.tsx create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/ShareBrain.tsx create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/__tests__/ShareBrain.test.tsx create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/components/InvitedUserRow.tsx create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/components/index.ts create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/hooks/useShareBrain.ts create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/index.ts create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/utils/generateBrainAssignation.ts create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/index.ts create mode 100644 frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types.ts create mode 100644 frontend/lib/components/ui/Select.tsx diff --git a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/BrainActions.tsx b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/BrainActions.tsx index 4cba45a62..85b15a1c8 100644 --- a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/BrainActions.tsx +++ b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/BrainActions.tsx @@ -1,28 +1,16 @@ import { UUID } from "crypto"; -import { MdDelete } from "react-icons/md"; -import Button from "@/lib/components/ui/Button"; -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -import { ShareBrain } from "./ShareBrain"; +import { DeleteBrain, ShareBrain } from "./components"; type BrainActionsProps = { brainId: UUID; }; export const BrainActions = ({ brainId }: BrainActionsProps): JSX.Element => { - const { deleteBrain } = useBrainContext(); - return (
- +
); }; diff --git a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/ShareBrain.tsx b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/ShareBrain.tsx deleted file mode 100644 index 7c7eef7ef..000000000 --- a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/ShareBrain.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { UUID } from "crypto"; -import { MdContentPaste, MdShare } from "react-icons/md"; - -import Button from "@/lib/components/ui/Button"; -import Modal from "@/lib/components/ui/Modal"; -import { useToast } from "@/lib/hooks"; - -type ShareBrainModalProps = { - brainId: UUID; -}; - -export const ShareBrain = ({ brainId }: ShareBrainModalProps): JSX.Element => { - const { publish } = useToast(); - - const baseUrl = window.location.origin; - const brainShareLink = `${baseUrl}/brain_subscription_invitation=${brainId}`; - - const handleCopyInvitationLink = async () => { - await navigator.clipboard.writeText(brainShareLink); - publish({ - variant: "success", - text: "Copied to clipboard", - }); - }; - - return ( - void 0} - variant={"tertiary"} - > - - - } - title="Share brain" - > -
-

{brainShareLink}

- -
-
- ); -}; diff --git a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/DeleteBrain.tsx b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/DeleteBrain.tsx new file mode 100644 index 000000000..0b1057393 --- /dev/null +++ b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/DeleteBrain.tsx @@ -0,0 +1,19 @@ +import { UUID } from "crypto"; +import { MdDelete } from "react-icons/md"; + +import Button from "@/lib/components/ui/Button"; +import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; + +export const DeleteBrain = ({ brainId }: { brainId: UUID }): JSX.Element => { + const { deleteBrain } = useBrainContext(); + + return ( + + ); +}; diff --git a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/ShareBrain.tsx b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/ShareBrain.tsx new file mode 100644 index 000000000..befe7abaa --- /dev/null +++ b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/ShareBrain.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { UUID } from "crypto"; +import { ImUserPlus } from "react-icons/im"; +import { MdContentPaste, MdShare } from "react-icons/md"; + +import Button from "@/lib/components/ui/Button"; +import Modal from "@/lib/components/ui/Modal"; + +import { InvitedUserRow } from "./components/InvitedUserRow"; +import { useShareBrain } from "./hooks/useShareBrain"; + +type ShareBrainModalProps = { + brainId: UUID; +}; + +export const ShareBrain = ({ brainId }: ShareBrainModalProps): JSX.Element => { + const { + roleAssignations, + brainShareLink, + handleCopyInvitationLink, + updateRoleAssignation, + removeRoleAssignation, + inviteUsers, + addNewRoleAssignationRole, + } = useShareBrain(brainId); + + const canAddNewRow = + roleAssignations.length === 0 || + roleAssignations.filter((invitingUser) => invitingUser.email === "") + .length === 0; + + return ( + void 0} + variant={"tertiary"} + data-testId="share-brain-button" + > + + + } + CloseTrigger={
} + title="Share brain" + > +
{ + event.preventDefault(); + void inviteUsers(); + }} + > +
+
+
+
+

{brainShareLink}

+
+ +
+
+ +
+ + {roleAssignations.map((roleAssignation, index) => ( + + ))} + +
+ +
+ +
+ + + ); +}; diff --git a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/__tests__/ShareBrain.test.tsx b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/__tests__/ShareBrain.test.tsx new file mode 100644 index 000000000..f5974b82e --- /dev/null +++ b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/__tests__/ShareBrain.test.tsx @@ -0,0 +1,50 @@ +import { fireEvent, render } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { ShareBrain } from "../ShareBrain"; + +describe("ShareBrain", () => { + it("should render ShareBrain component properly", () => { + const { getByTestId } = render( + + ); + const shareButton = getByTestId("share-brain-button"); + expect(shareButton).toBeDefined(); + }); + + it("should render open share modal when share button is clicked", () => { + const { getByText, getByTestId } = render( + + ); + const shareButton = getByTestId("share-brain-button"); + fireEvent.click(shareButton); + expect(getByText("Share brain")).toBeDefined(); + }); + + it('shoud add new user row when "Add new user" button is clicked and only where there is no empty field', async () => { + const { getByTestId, findAllByTestId } = render( + + ); + const shareButton = getByTestId("share-brain-button"); + fireEvent.click(shareButton); + + let assignationRows = await findAllByTestId("assignation-row"); + + expect(assignationRows.length).toBe(1); + + const firstAssignationRowEmailInput = ( + await findAllByTestId("role-assignation-email-input") + )[0]; + + fireEvent.change(firstAssignationRowEmailInput, { + target: { value: "user@quivr.app" }, + }); + + const addNewRoleAssignationButton = getByTestId("add-new-row-role-button"); + fireEvent.click(addNewRoleAssignationButton); + + assignationRows = await findAllByTestId("assignation-row"); + + expect(assignationRows.length).toBe(2); + }); +}); diff --git a/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/components/InvitedUserRow.tsx b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/components/InvitedUserRow.tsx new file mode 100644 index 000000000..946842c72 --- /dev/null +++ b/frontend/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/components/ShareBrain/components/InvitedUserRow.tsx @@ -0,0 +1,69 @@ +import { useEffect, useState } from "react"; +import { MdOutlineRemoveCircle } from "react-icons/md"; + +import Field from "@/lib/components/ui/Field"; +import { Select } from "@/lib/components/ui/Select"; + +import { BrainRoleAssignation, BrainRoleType } from "../../../types"; + +type AddUserRowProps = { + onChange: (newRole: BrainRoleAssignation) => void; + removeCurrentInvitation?: () => void; + roleAssignation: BrainRoleAssignation; +}; + +type SelectOptionsProps = { + label: string; + value: BrainRoleType; +}; +const SelectOptions: SelectOptionsProps[] = [ + { label: "Viewer", value: "viewer" }, + { label: "Editor", value: "editor" }, +]; + +export const InvitedUserRow = ({ + onChange, + removeCurrentInvitation, + roleAssignation, +}: AddUserRowProps): JSX.Element => { + const [selectedRole, setSelectedRole] = useState( + roleAssignation.role + ); + const [email, setEmail] = useState(roleAssignation.email); + + useEffect(() => { + onChange({ + ...roleAssignation, + email, + role: selectedRole, + }); + }, [email, selectedRole]); + + return ( +
+
+ +
+
+ setEmail(e.target.value)} + value={email} + onBlur={() => email === "" && removeCurrentInvitation?.()} + data-testid="role-assignation-email-input" + /> +
+