From 62cbb41b1f81d140562d297ae4596a144a885497 Mon Sep 17 00:00:00 2001 From: eagle Date: Thu, 9 May 2024 19:19:23 +0530 Subject: [PATCH 1/8] feat: add manage team tab in round view --- .../src/features/round/ViewManageTeam.tsx | 7 +++++ .../src/features/round/ViewRoundPage.tsx | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/round-manager/src/features/round/ViewManageTeam.tsx diff --git a/packages/round-manager/src/features/round/ViewManageTeam.tsx b/packages/round-manager/src/features/round/ViewManageTeam.tsx new file mode 100644 index 0000000000..d92edc3f31 --- /dev/null +++ b/packages/round-manager/src/features/round/ViewManageTeam.tsx @@ -0,0 +1,7 @@ +export default function ViewManageTeam() { + return ( +
+

Manage Team

+
+ ); +} diff --git a/packages/round-manager/src/features/round/ViewRoundPage.tsx b/packages/round-manager/src/features/round/ViewRoundPage.tsx index 4e8013319d..732bd95b79 100644 --- a/packages/round-manager/src/features/round/ViewRoundPage.tsx +++ b/packages/round-manager/src/features/round/ViewRoundPage.tsx @@ -11,6 +11,7 @@ import { DocumentTextIcon, InboxIcon, UserGroupIcon, + UserAddIcon, } from "@heroicons/react/solid"; import { Button } from "common/src/styles"; import { Link, useParams } from "react-router-dom"; @@ -52,6 +53,7 @@ import { getRoundStrategyType } from "common"; import { useApplicationsByRoundId } from "../common/useApplicationsByRoundId"; import AlloV1 from "common/src/icons/AlloV1"; import AlloV2 from "common/src/icons/AlloV2"; +import ViewManageTeam from "./ViewManageTeam"; export const isDirectRound = (round: Round | undefined) => { return ( @@ -221,6 +223,29 @@ export default function ViewRoundPage() { )} + + verticalTabStyles(selected) + } + > + {({ selected }) => ( +
+ + + Manage Team + +
+ )} +
{!isDirectRound(round) && ( @@ -340,6 +365,9 @@ export default function ViewRoundPage() { + + + {!isDirectRound(round) && ( <> From 60021200e0d09ba9d4bd6fc62189d2f48aabb805 Mon Sep 17 00:00:00 2001 From: eagle Date: Thu, 9 May 2024 19:46:12 +0530 Subject: [PATCH 2/8] feat: update ViewManageTeam --- .../src/features/round/ViewManageTeam.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/round-manager/src/features/round/ViewManageTeam.tsx b/packages/round-manager/src/features/round/ViewManageTeam.tsx index d92edc3f31..2d4d0a621a 100644 --- a/packages/round-manager/src/features/round/ViewManageTeam.tsx +++ b/packages/round-manager/src/features/round/ViewManageTeam.tsx @@ -1,7 +1,15 @@ export default function ViewManageTeam() { return (
-

Manage Team

+

Manage Team

+

+ Add or remove admins and operators to your team.{" "} +

+

+ Make sure to have at least two admins at all times for security + purposes. +

+

View Members

); } From 16625c4a09bcd2865f70c34e485d179b0631d08d Mon Sep 17 00:00:00 2001 From: eagle Date: Mon, 13 May 2024 18:29:29 +0530 Subject: [PATCH 3/8] feat: update round type --- packages/round-manager/src/features/api/round.ts | 1 + packages/round-manager/src/features/api/types.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/round-manager/src/features/api/round.ts b/packages/round-manager/src/features/api/round.ts index 0b54e32ebe..6b1347ef60 100644 --- a/packages/round-manager/src/features/api/round.ts +++ b/packages/round-manager/src/features/api/round.ts @@ -128,6 +128,7 @@ function indexerV2RoundToRound(round: RoundForManager): Round { }, ownedBy: round.projectId, operatorWallets: operatorWallets, + roles: round.roles, finalized: false, tags: round.tags, createdByAddress: round.createdByAddress, diff --git a/packages/round-manager/src/features/api/types.ts b/packages/round-manager/src/features/api/types.ts index 183432184b..d7e61d2982 100644 --- a/packages/round-manager/src/features/api/types.ts +++ b/packages/round-manager/src/features/api/types.ts @@ -8,7 +8,7 @@ import { RoundVisibilityType } from "common"; import { BigNumber } from "ethers"; import { Address } from "viem"; import { SchemaQuestion } from "./utils"; -import { RoundForManager, SybilDefense } from "data-layer"; +import { AddressAndRole, RoundForManager, SybilDefense } from "data-layer"; export type Network = "optimism" | "fantom" | "pgn"; @@ -239,6 +239,10 @@ export interface Round { * Addresses of wallets that will have admin privileges to operate the Grant program */ operatorWallets?: Array; + /** + * List of addresses and their roles in the round + */ + roles?: AddressAndRole[]; /** * List of projects approved for the round */ From 919a1a5974ed544fbf9284c5f186745c1682da98 Mon Sep 17 00:00:00 2001 From: eagle Date: Mon, 13 May 2024 18:30:05 +0530 Subject: [PATCH 4/8] feat: add team table --- .../src/features/round/ViewManageTeam.tsx | 61 ++++++++++++++++++- .../src/features/round/ViewRoundPage.tsx | 2 +- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/packages/round-manager/src/features/round/ViewManageTeam.tsx b/packages/round-manager/src/features/round/ViewManageTeam.tsx index 2d4d0a621a..0309eabee0 100644 --- a/packages/round-manager/src/features/round/ViewManageTeam.tsx +++ b/packages/round-manager/src/features/round/ViewManageTeam.tsx @@ -1,4 +1,20 @@ -export default function ViewManageTeam() { +import { AddressAndRole } from "data-layer"; +import { Round } from "../api/types"; +import { useMemo } from "react"; + +const sortDataByRole = (data: AddressAndRole[]): AddressAndRole[] => { + return data.sort((a, b) => { + if (a.role === "ADMIN") return -1; + if (b.role === "ADMIN") return 1; + return a.role.localeCompare(b.role); + }); +}; + +export default function ViewManageTeam(props: { round: Round | undefined }) { + const sortedRoles = useMemo(() => { + return sortDataByRole(props.round?.roles || []); + }, [props.round?.roles]); + return (

Manage Team

@@ -9,7 +25,48 @@ export default function ViewManageTeam() { Make sure to have at least two admins at all times for security purposes.

-

View Members

+

View Members

+
+ + + + + + + + + + {sortedRoles.map((item: AddressAndRole, index) => ( + + + + + + ))} + +
+ Name + + Wallet address + + Role +
+ User + + {item.address} + + {item.role === "ADMIN" ? "Admin" : "Operator"} +
+
); } diff --git a/packages/round-manager/src/features/round/ViewRoundPage.tsx b/packages/round-manager/src/features/round/ViewRoundPage.tsx index 732bd95b79..c31558cc84 100644 --- a/packages/round-manager/src/features/round/ViewRoundPage.tsx +++ b/packages/round-manager/src/features/round/ViewRoundPage.tsx @@ -366,7 +366,7 @@ export default function ViewRoundPage() {
- + {!isDirectRound(round) && ( <> From c39bbec948d8fed1308dbf3af7093a614684bf30 Mon Sep 17 00:00:00 2001 From: eagle Date: Mon, 13 May 2024 19:22:31 +0530 Subject: [PATCH 5/8] fix: alignment --- packages/round-manager/src/features/round/ViewManageTeam.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/round-manager/src/features/round/ViewManageTeam.tsx b/packages/round-manager/src/features/round/ViewManageTeam.tsx index 0309eabee0..e6d82599a9 100644 --- a/packages/round-manager/src/features/round/ViewManageTeam.tsx +++ b/packages/round-manager/src/features/round/ViewManageTeam.tsx @@ -32,7 +32,7 @@ export default function ViewManageTeam(props: { round: Round | undefined }) { Name @@ -53,7 +53,7 @@ export default function ViewManageTeam(props: { round: Round | undefined }) { {sortedRoles.map((item: AddressAndRole, index) => ( - + User From 4a70dfa18fea0a5f2fdbfe6f76940c21bf16c304 Mon Sep 17 00:00:00 2001 From: eagle Date: Tue, 14 May 2024 18:10:43 +0530 Subject: [PATCH 6/8] feat: fetch ens names & update tests --- .../src/features/round/ViewManageTeam.tsx | 28 +++++++++++++------ .../round/__tests__/ViewRoundPage.test.tsx | 4 ++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/round-manager/src/features/round/ViewManageTeam.tsx b/packages/round-manager/src/features/round/ViewManageTeam.tsx index e6d82599a9..70e7d0bf9e 100644 --- a/packages/round-manager/src/features/round/ViewManageTeam.tsx +++ b/packages/round-manager/src/features/round/ViewManageTeam.tsx @@ -1,6 +1,7 @@ import { AddressAndRole } from "data-layer"; import { Round } from "../api/types"; import { useMemo } from "react"; +import { useEnsName } from "wagmi"; const sortDataByRole = (data: AddressAndRole[]): AddressAndRole[] => { return data.sort((a, b) => { @@ -18,12 +19,9 @@ export default function ViewManageTeam(props: { round: Round | undefined }) { return (

Manage Team

+

View who is on your team.

- Add or remove admins and operators to your team.{" "} -

-

- Make sure to have at least two admins at all times for security - purposes. + Only admins can add others to your team.

View Members

@@ -54,11 +52,9 @@ export default function ViewManageTeam(props: { round: Round | undefined }) { {sortedRoles.map((item: AddressAndRole, index) => ( - User - - - {item.address} + User{index + 1} + {item.role === "ADMIN" ? "Admin" : "Operator"} @@ -70,3 +66,17 @@ export default function ViewManageTeam(props: { round: Round | undefined }) {
); } + +function AddressRow(props: { address: string }) { + const { data: ensName } = useEnsName({ + address: props.address as `0x${string}`, + chainId: 1, + }); + console.log("ensName", ensName); + + return ( + + {ensName || props.address} + + ); +} diff --git a/packages/round-manager/src/features/round/__tests__/ViewRoundPage.test.tsx b/packages/round-manager/src/features/round/__tests__/ViewRoundPage.test.tsx index f3a8cbe232..37533bb78e 100644 --- a/packages/round-manager/src/features/round/__tests__/ViewRoundPage.test.tsx +++ b/packages/round-manager/src/features/round/__tests__/ViewRoundPage.test.tsx @@ -30,7 +30,7 @@ jest.mock("@rainbow-me/rainbowkit", () => ({ jest.mock("data-layer", () => ({ ...jest.requireActual("data-layer"), useDataLayer: () => ({ - getRoundById: jest.fn(), + getRoundById: jest.fn(), }), })); @@ -173,6 +173,7 @@ describe("View Round", () => { expect(screen.getByText("Fund Round")).toBeInTheDocument(); expect(screen.getByText("Grant Applications")).toBeInTheDocument(); expect(screen.getByText("Round Settings")).toBeInTheDocument(); + expect(screen.getByText("Manage Team")).toBeInTheDocument(); expect(screen.getByText("Round Stats")).toBeInTheDocument(); expect(screen.getByText("Round Results")).toBeInTheDocument(); expect(screen.getByText("Fund Grantees")).toBeInTheDocument(); @@ -200,6 +201,7 @@ describe("View Round", () => { expect(screen.getByTestId("side-nav-bar")).toBeInTheDocument(); expect(screen.getByText("Grant Applications")).toBeInTheDocument(); expect(screen.getByText("Round Settings")).toBeInTheDocument(); + expect(screen.getByText("Manage Team")).toBeInTheDocument(); expect(screen.queryAllByText("Fund Contract").length).toBe(0); expect(screen.queryAllByText("Round Stats").length).toBe(0); expect(screen.queryAllByText("Round Results").length).toBe(0); From 238dd320e9515987bc754501c3c9a60a3ccbb16e Mon Sep 17 00:00:00 2001 From: eagle Date: Tue, 14 May 2024 19:04:11 +0530 Subject: [PATCH 7/8] chore: rm console log --- packages/round-manager/src/features/round/ViewManageTeam.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/round-manager/src/features/round/ViewManageTeam.tsx b/packages/round-manager/src/features/round/ViewManageTeam.tsx index 70e7d0bf9e..e8ca5e3494 100644 --- a/packages/round-manager/src/features/round/ViewManageTeam.tsx +++ b/packages/round-manager/src/features/round/ViewManageTeam.tsx @@ -72,7 +72,6 @@ function AddressRow(props: { address: string }) { address: props.address as `0x${string}`, chainId: 1, }); - console.log("ensName", ensName); return ( From 4185aef2e9b6b684d43bae2b8a9c7b441199520a Mon Sep 17 00:00:00 2001 From: eagle Date: Thu, 16 May 2024 18:11:34 +0530 Subject: [PATCH 8/8] feat: filter roles --- .../src/features/round/ViewManageTeam.tsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/round-manager/src/features/round/ViewManageTeam.tsx b/packages/round-manager/src/features/round/ViewManageTeam.tsx index e8ca5e3494..8d63af7e81 100644 --- a/packages/round-manager/src/features/round/ViewManageTeam.tsx +++ b/packages/round-manager/src/features/round/ViewManageTeam.tsx @@ -11,11 +11,38 @@ const sortDataByRole = (data: AddressAndRole[]): AddressAndRole[] => { }); }; +const filterRoles = (data: AddressAndRole[]): AddressAndRole[] => { + return data.reduce((acc: AddressAndRole[], current) => { + const existingIndex = acc.findIndex( + (item) => item.address === current.address + ); + + // Check if this address is already included + if (existingIndex === -1) { + // If not included, simply add the current item + acc.push(current); + } else if ( + acc[existingIndex].role !== "ADMIN" && + current.role === "ADMIN" + ) { + // If the existing item is not an Admin but the current is, replace it + acc[existingIndex] = current; + } + return acc; + }, []); +}; + export default function ViewManageTeam(props: { round: Round | undefined }) { + // Show Admin role first, then Operator const sortedRoles = useMemo(() => { return sortDataByRole(props.round?.roles || []); }, [props.round?.roles]); + // If an address is an Admin & Operator, only show Admin in the UI + const filteredRoles = useMemo(() => { + return filterRoles(sortedRoles); + }, [sortedRoles]); + return (

Manage Team

@@ -49,7 +76,7 @@ export default function ViewManageTeam(props: { round: Round | undefined }) { - {sortedRoles.map((item: AddressAndRole, index) => ( + {filteredRoles.map((item: AddressAndRole, index) => ( User{index + 1}