diff --git a/src/app/pool/[chain]/[id]/page.tsx b/src/app/pool/[chain]/[id]/page.tsx
index 57ff537..92cf290 100644
--- a/src/app/pool/[chain]/[id]/page.tsx
+++ b/src/app/pool/[chain]/[id]/page.tsx
@@ -1,6 +1,13 @@
import PoolDetailPage from "@/components/Pool/PoolDetail";
-import { IPoolDetailResponse, TPoolDetail } from "@/components/Pool/types";
+import {
+ IPoolDetailResponse,
+ Status,
+ StrategyId,
+ TPoolDetail,
+ TRfpStrategy,
+} from "@/components/Pool/types";
import { getPoolDetailDataQuery, graphqlEndpoint } from "@/utils/query";
+import { fetchIpfsMetadata } from "@/utils/utils";
import { request } from "graphql-request";
export default async function PoolDetail({
@@ -8,31 +15,108 @@ export default async function PoolDetail({
}: {
params: { chain: string; id: string };
}) {
+ // ============ todo: replace dummy data with real data ============
+ const rfpStrategyDetails: TRfpStrategy = {
+ useRegistryAnchor: true,
+ metadataRequired: true,
+ acceptedRecipient: "0x1234567890123456789012345678901234567890",
+ maxBid: 2000000000000000,
+ upcomingMilsetoneId: 1,
+ recipients: [
+ {
+ recipient: "0x1234567890123456789012345678901234567890",
+ useRegistryAnchor: true,
+ recipientAddress: "0x1234567890123456789012345678901234567890",
+ proposalBid: 1000000000000000,
+ recipientStatus: Status.Accepted,
+ },
+ {
+ recipient: "0x1234567890123456789012345678901234567890",
+ useRegistryAnchor: true,
+ recipientAddress: "0x1234567890123456789012345678901234567890",
+ proposalBid: 1000000000000000,
+ recipientStatus: Status.Pending,
+ },
+ ],
+ milestones: [
+ {
+ amountPercentage: 10,
+ metadata: {
+ protocol: 1,
+ pointer:
+ "bafkreigwiljyskihuaeyjsedoei3taprwbbheldxig25lhoqvw2kpcf4bu",
+ },
+ metadataObj: await fetchIpfsMetadata({
+ protocol: 1,
+ pointer:
+ "bafkreigwiljyskihuaeyjsedoei3taprwbbheldxig25lhoqvw2kpcf4bu",
+ }),
+ milestoneStatus: Status.Pending,
+ },
+ {
+ amountPercentage: 10,
+ metadata: {
+ protocol: 1,
+ pointer:
+ "bafkreigwiljyskihuaeyjsedoei3taprwbbheldxig25lhoqvw2kpcf4bu",
+ },
+ metadataObj: await fetchIpfsMetadata({
+ protocol: 1,
+ pointer:
+ "bafkreigwiljyskihuaeyjsedoei3taprwbbheldxig25lhoqvw2kpcf4bu",
+ }),
+ milestoneStatus: Status.Pending,
+ },
+ ],
+ distributions: [
+ {
+ acceptedRecipient: "0x1234567890123456789012345678901234567890",
+ recipientAddress: "0x1234567890123456789012345678901234567890",
+ amount: 1000000000000000,
+ sender: "0x1234567890123456789012345678901234567890",
+ },
+ {
+ acceptedRecipient: "0x1234567890123456789012345678901234567890",
+ recipientAddress: "0x1234567890123456789012345678901234567890",
+ amount: 1000000000000000,
+ sender: "0x1234567890123456789012345678901234567890",
+ },
+ ],
+ votes: [
+ {
+ voter: "0x1234567890123456789012345678901234567890",
+ recipientId: "0x1234567890123456789012345678901234567890",
+ timestamp: 1234567890,
+ },
+ {
+ voter: "0x1234567890123456789012345678901234567890",
+ recipientId: "0x1234567890123456789012345678901234567890",
+ timestamp: 1234567890,
+ },
+ ],
+ };
+
const response: IPoolDetailResponse = await request(
graphqlEndpoint,
getPoolDetailDataQuery,
{
chainId: params.chain,
poolId: params.id,
- }
+ },
);
const { pool }: { pool: TPoolDetail } = response;
- let poolMetadata = "{}";
-
- try {
- const response = await fetch(
- `https://gitcoin.mypinata.cloud/ipfs/${pool.metadataPointer}`,
- );
+ if (pool.poolId === "42")
+ pool.strategyDetails = {
+ strategyId: StrategyId.RFPCommittee,
+ details: rfpStrategyDetails,
+ };
- // Check if the response status is OK (200)
- if (response.ok) {
- poolMetadata = await response.text();
- }
- } catch (error) {
- console.error(error);
- }
+ const metadataObj: Object = await fetchIpfsMetadata({
+ pointer: pool.metadataPointer,
+ protocol: pool.metadataProtocol,
+ });
- return ;
+ return ;
}
diff --git a/src/app/profile/[chain]/[id]/page.tsx b/src/app/profile/[chain]/[id]/page.tsx
index b49c91f..654a1e1 100644
--- a/src/app/profile/[chain]/[id]/page.tsx
+++ b/src/app/profile/[chain]/[id]/page.tsx
@@ -4,6 +4,7 @@ import {
TProfileDetail,
} from "@/components/Registry/types";
import { getProfileDetailDataQuery, graphqlEndpoint } from "@/utils/query";
+import { fetchIpfsMetadata } from "@/utils/utils";
import request from "graphql-request";
export default async function ProfileDetailPage({
@@ -21,18 +22,14 @@ export default async function ProfileDetailPage({
);
const profile: TProfileDetail = profileDetails.profile;
-
- const response = await fetch(
- `https://gitcoin.mypinata.cloud/ipfs/${profile.metadataPointer}`,
- );
-
- let metadata = "";
-
- if (response.ok) metadata = await response.text();
+ const metadataObj: Object = await fetchIpfsMetadata({
+ pointer: profile.metadataPointer,
+ protocol: profile.metadataProtocol,
+ });
return (
);
}
diff --git a/src/components/List.tsx b/src/components/List.tsx
new file mode 100644
index 0000000..bf16a48
--- /dev/null
+++ b/src/components/List.tsx
@@ -0,0 +1,35 @@
+import { useMediaQuery } from "@/hooks/useMediaQuery";
+import { TListProps } from "@/types/types";
+
+const List = (props: {data: TListProps[]}) => {
+ const isMobile = useMediaQuery(768);
+ const py = isMobile ? "py-2" : "py-6";
+
+ return (
+
+
+ {props.data.map((d, index) => (
+
+
-
+ {d.label}
+
+ -
+ {d.value}
+
+
+ ))}
+
+
+ );
+};
+
+export default List;
diff --git a/src/components/Metadata.tsx b/src/components/Metadata.tsx
new file mode 100644
index 0000000..e3fb27b
--- /dev/null
+++ b/src/components/Metadata.tsx
@@ -0,0 +1,43 @@
+import { Metadata } from "@/types/types";
+import JsonView from "@uiw/react-json-view";
+import { TbExternalLink } from "react-icons/tb";
+import { truncatedString } from "./Address";
+
+const Metadata = ({
+ isMobile,
+ metadata,
+ metadataObj,
+ shortenTexteAfterLength = 120,
+ collapsed = 2,
+}: {
+ isMobile: boolean;
+ metadata: Metadata;
+ metadataObj: Object;
+ shortenTexteAfterLength?: number;
+ collapsed?: number;
+}) => {
+ return (
+
+
+ {isMobile ? truncatedString(metadata.pointer) : metadata.pointer}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Metadata;
diff --git a/src/components/Pool/PoolDetail.tsx b/src/components/Pool/PoolDetail.tsx
index d49bbcb..efd5d37 100644
--- a/src/components/Pool/PoolDetail.tsx
+++ b/src/components/Pool/PoolDetail.tsx
@@ -1,33 +1,123 @@
"use client";
-import { convertChainIdToNetworkName } from "@/utils/utils";
+import { amountString, convertChainIdToNetworkName } from "@/utils/utils";
import { AddressResponsive, truncatedString } from "../Address";
-import { TPoolDetail } from "./types";
-import { MetadataProtocol } from "@/types/types";
-import { TbExternalLink } from "react-icons/tb";
-import JsonView from "@uiw/react-json-view";
+import { StrategyId, TPoolDetail } from "./types";
+import { MetadataProtocol, TListProps } from "@/types/types";
import { ethers } from "ethers";
import Link from "next/link";
import { getNetworks } from "@/utils/networks";
import { useMediaQuery } from "@/hooks/useMediaQuery";
+import List from "../List";
+import Metadata from "../Metadata";
+import RfpDetails from "./Strategies/RfpDetails";
const PoolDetailPage = ({
pool,
- poolMetadata,
+ metadataObj,
}: {
pool: TPoolDetail;
- poolMetadata: string;
+ metadataObj: Object;
}) => {
- let metadataObj;
- try {
- metadataObj = JSON.parse(poolMetadata);
- } catch (error) {
- metadataObj = {
- error: "Error parsing metadata",
- };
- }
- const isMobile = useMediaQuery(768);
- const py = isMobile ? "py-2" : "py-6";
+ const isMobile = useMediaQuery(768);
+
+ const listProps: TListProps[] = [
+ {
+ label: "Strategy",
+ value: (
+
+ ),
+ },
+ {
+ label: "Network",
+ value: convertChainIdToNetworkName(Number(pool.chainId)),
+ },
+ {
+ label: "Token",
+ value: (
+
+ ),
+ },
+ {
+ label: "Amount",
+ value: amountString(
+ pool.amount,
+ pool.tokenMetadata,
+ Number(pool.chainId),
+ ),
+ },
+ {
+ label: "Creator",
+ value: (
+
+ ),
+ },
+ {
+ label: "Profile",
+ value: (
+ <>
+ {pool.profile.name}
+
+
+
+
+ {isMobile
+ ? truncatedString(pool.profile.profileId)
+ : pool.profile.profileId}{" "}
+
+
+ >
+ ),
+ },
+ {
+ label: "Created at",
+ value: new Date(pool.createdAt).toLocaleString(),
+ },
+ {
+ label: "Updated at",
+ value: new Date(pool.updatedAt).toLocaleString(),
+ },
+ {
+ label: "Metadata (" + MetadataProtocol[pool.metadataProtocol] + ")",
+ value: (
+
+ ),
+ },
+ ];
+
+ const renderDetails = () => {
+ if (!pool.strategyDetails) return null;
+
+ switch (pool.strategyDetails.strategyId) {
+ case StrategyId.RFPSimple:
+ case StrategyId.RFPCommittee:
+ return (
+
+ );
+ default:
+ return null;
+ }
+ };
return (
@@ -48,124 +138,8 @@ const PoolDetailPage = ({
-
-
-
-
-
-
- Network
-
- -
- {convertChainIdToNetworkName(Number(pool.chainId))}
-
-
-
-
-
-
- Amount
-
- -
- {ethers.formatUnits(
- pool.amount ?? 0,
- pool.tokenMetadata.decimals ?? 18,
- )}{" "}
- {pool.tokenMetadata.symbol ??
- getNetworks()[Number(pool.chainId)].symbol}
-
-
-
-
-
-
- Profile
-
- -
- {pool.profile.name}
-
-
-
-
- {isMobile
- ? truncatedString(pool.profile.profileId)
- : pool.profile.profileId}{" "}
-
-
-
-
-
-
-
- Created at
-
- -
- {new Date(pool.createdAt).toLocaleString()}
-
-
-
-
-
- Updated at
-
- -
- {new Date(pool.updatedAt).toLocaleString()}
-
-
-
-
-
- Metadata ({MetadataProtocol[pool.metadataProtocol]}){" "}
-
-
-
-
- {isMobile
- ? truncatedString(pool.metadataPointer)
- : pool.metadataPointer}
-
-
-
-
-
-
-
-
-
-
-
+
+ {renderDetails()}
);
};
diff --git a/src/components/Pool/Strategies/RfpDetails.tsx b/src/components/Pool/Strategies/RfpDetails.tsx
new file mode 100644
index 0000000..6048516
--- /dev/null
+++ b/src/components/Pool/Strategies/RfpDetails.tsx
@@ -0,0 +1,186 @@
+import { TListProps, TTableData } from "@/types/types";
+import { Status, TRfpStrategy, TTokenMetadata } from "../types";
+import List from "@/components/List";
+import { Address, AddressResponsive } from "@/components/Address";
+import { amountString } from "@/utils/utils";
+import Table from "@/components/Table";
+import Metadata from "@/components/Metadata";
+import { useMediaQuery } from "@/hooks/useMediaQuery";
+
+const RfpDetails = ({
+ details,
+ tokenMetadata,
+ chainId,
+}: {
+ details: TRfpStrategy;
+ tokenMetadata: TTokenMetadata;
+ chainId: number;
+}) => {
+ const isMobile = useMediaQuery(768);
+ const recipientsTable: TTableData = {
+ headers: [
+ "Recipient",
+ "Recipient Address",
+ "Proposal Bid",
+ "Status",
+ "Registry Anchor",
+ ],
+ rows: details.recipients.map((r) => [
+ // eslint-disable-next-line react/jsx-key
+ ,
+ // eslint-disable-next-line react/jsx-key
+ ,
+ amountString(r.proposalBid, tokenMetadata, chainId),
+ r.recipientStatus.toString(),
+ r.useRegistryAnchor ? "Yes" : "No",
+ ]),
+ };
+
+ const milestonesTable: TTableData = {
+ headers: ["Amount", "Metadata", "Status"],
+ rows: details.milestones.map((m) => [
+ m.amountPercentage.toString() + "%",
+ // eslint-disable-next-line react/jsx-key
+ ,
+ Status[m.milestoneStatus],
+ ]),
+ };
+
+ const distributionsTable: TTableData = {
+ headers: ["Recipient", "Recipient Address", "Amount", "Sender"],
+ rows: details.distributions.map((r) => [
+ // eslint-disable-next-line react/jsx-key
+ ,
+ // eslint-disable-next-line react/jsx-key
+ ,
+ amountString(r.amount, tokenMetadata, chainId),
+ // eslint-disable-next-line react/jsx-key
+ ,
+ ]),
+ };
+
+ const voteTable: TTableData = {
+ headers: ["Voter", "Recipient", "Timestamp"],
+ rows: details.votes.map((v) => [
+ // eslint-disable-next-line react/jsx-key
+ ,
+ // eslint-disable-next-line react/jsx-key
+ ,
+ // eslint-disable-next-line react/jsx-key
+ new Date(v.timestamp).toLocaleString(),
+ ]),
+ };
+
+ const listProps: TListProps[] = [
+ {
+ label: "Use Registry Anchor",
+ value: details.useRegistryAnchor ? "Yes" : "No",
+ },
+ {
+ label: "Metadata Required",
+ value: details.metadataRequired ? "Yes" : "No",
+ },
+ {
+ label: "Accepted Recipient",
+ value: (
+ // eslint-disable-next-line react/jsx-key
+
+ ),
+ },
+ {
+ label: "Max Bid",
+ value: amountString(details.maxBid, tokenMetadata, chainId),
+ },
+ {
+ label: "Upcoming Milestone Id",
+ value: details.upcomingMilsetoneId.toString(),
+ },
+ ];
+
+ return (
+
+
+
+ RFP Strategy Details
+
+
+
+
+
+
+ {details.recipients.length > 0 && (
+
+ )}
+
+ {details.milestones.length > 0 && (
+
+ )}
+
+ {details.distributions.length > 0 && (
+
+ )}
+ {details.votes.length > 0 && (
+
+ )}
+
+ );
+};
+
+export default RfpDetails;
diff --git a/src/components/Pool/types.ts b/src/components/Pool/types.ts
index 47b62da..46dd381 100644
--- a/src/components/Pool/types.ts
+++ b/src/components/Pool/types.ts
@@ -1,3 +1,4 @@
+import { Metadata } from "@/types/types";
import { TProfile } from "../Registry/types";
export type TPool = {
@@ -15,6 +16,10 @@ export type TPoolDetail = TPool & {
tokenMetadata: TTokenMetadata;
updatedAt: string;
createdAt: string;
+ strategyDetails?: {
+ strategyId: StrategyId;
+ details: TRfpStrategy;
+ };
};
export type TTokenMetadata = {
@@ -30,3 +35,63 @@ export interface IPoolsResponse {
export interface IPoolDetailResponse {
pool: TPoolDetail;
}
+
+export enum Status {
+ "None",
+ "Pending",
+ "Accepted",
+ "Rejected",
+ "Appealed",
+ "Cancelled",
+}
+
+export enum StrategyId {
+ "None",
+ RFPSimple = "0xb87f34c0968bd74d43a6a5b72831a5ea733a4783a026b9fc9b1d17adf51214d2",
+ RFPCommittee = "0x414f2ea9b91b8ee2e35a380fa0af0e14079832cc93530a61a4893b3dbf0a9aba",
+ QVSimple = "0xed28ce0387d1786c1a38404047e9eecc4d1dcaeff695b867e912483e36c3d770",
+ DonationVotingMerkleDistributionDirectTransfer = "0xc5263e972c91d7ff40708bc71239a2b6cbc8768704e210ca3069e2e11fc195df",
+ DonationVotingMerkleDistributionVault = "0xecc48557f4826bd1181a4495232d6d07f248ef9cc0a650e64520f6c9f7458a8c",
+}
+
+// ==================== RFP ====================
+
+export type TRfpRecipient = {
+ recipient: string;
+ useRegistryAnchor: boolean;
+ recipientAddress: string;
+ proposalBid: number;
+ recipientStatus: Status;
+};
+
+export type TRfpMilestone = {
+ amountPercentage: number;
+ metadata: Metadata;
+ metadataObj: Object;
+ milestoneStatus: Status;
+};
+
+export type TRfpDistribution = {
+ acceptedRecipient: string;
+ recipientAddress: string;
+ amount: number;
+ sender: string;
+};
+
+export type RfpVote = {
+ voter: string;
+ recipientId: string;
+ timestamp: number;
+};
+
+export type TRfpStrategy = {
+ useRegistryAnchor: boolean;
+ metadataRequired: boolean;
+ acceptedRecipient: string;
+ maxBid: number;
+ upcomingMilsetoneId: number;
+ recipients: TRfpRecipient[];
+ milestones: TRfpMilestone[];
+ distributions: TRfpDistribution[];
+ votes: RfpVote[];
+};
diff --git a/src/components/Registry/ProfileDetail.tsx b/src/components/Registry/ProfileDetail.tsx
index 10581cf..d0d13dd 100644
--- a/src/components/Registry/ProfileDetail.tsx
+++ b/src/components/Registry/ProfileDetail.tsx
@@ -3,30 +3,95 @@
import { convertChainIdToNetworkName } from "@/utils/utils";
import { AddressResponsive, truncatedString } from "../Address";
import { TProfileDetail } from "./types";
-import { MetadataProtocol } from "@/types/types";
-import { TbExternalLink } from "react-icons/tb";
-import JsonView from "@uiw/react-json-view";
+import { MetadataProtocol, TListProps } from "@/types/types";
import Link from "next/link";
import Pool from "../Pool/Pool";
import { useMediaQuery } from "@/hooks/useMediaQuery";
+import List from "../List";
+import Metadata from "../Metadata";
const ProfileDetail = ({
profile,
- metadata,
+ metadataObj,
}: {
profile: TProfileDetail;
- metadata: string;
+ metadataObj: Object;
}) => {
- let metadataObj;
- try {
- metadataObj = JSON.parse(metadata ?? "");
- } catch (error) {
- metadataObj = {
- error: "Error parsing metadata",
- };
- }
const isMobile = useMediaQuery(768);
- const py = isMobile ? "py-2" : "py-6";
+
+ const listProps: TListProps[] = [
+ {
+ label: "Network",
+ value: convertChainIdToNetworkName(profile.chainId),
+ },
+ {
+ label: "Nonce",
+ value: profile.nonce.toString(),
+ },
+ {
+ label: "Anchor",
+ value: (
+
+ ),
+ },
+ {
+ label: "Creator",
+ value: (
+
+ ),
+ },
+ {
+ label: "Owner",
+ value: (
+
+ ),
+ },
+ {
+ label: "Members",
+ value: (
+
+ {profile.role.roleAccounts.map((account, index) => (
+ -
+
+
+ ))}
+
+ ),
+ },
+ {
+ label: "Created at",
+ value: new Date(profile.createdAt).toLocaleString(),
+ },
+ {
+ label: "Updated at",
+ value: new Date(profile.updatedAt).toLocaleString(),
+ },
+ {
+ label: `Metadata (${MetadataProtocol[profile.metadataProtocol]})`,
+ value: ,
+ },
+ ];
return (
@@ -47,128 +112,9 @@ const ProfileDetail = ({
-
-
-
-
-
- Network
-
- -
- {convertChainIdToNetworkName(profile.chainId)}
-
-
-
-
-
- Nonce
-
- -
- {profile.nonce}
-
-
-
-
-
-
-
-
- Members
-
-
-
-
- {profile.role.roleAccounts.map((account, index) => (
- -
-
-
- ))}
-
-
-
-
-
-
- Created at
-
- -
- {new Date(profile.createdAt).toLocaleString()}
-
-
-
-
-
- Updated at
-
- -
- {new Date(profile.updatedAt).toLocaleString()}
-
-
-
-
-
- Metadata ({MetadataProtocol[profile.metadataProtocol]}){" "}
-
-
-
-
- {isMobile
- ? truncatedString(profile.metadataPointer)
- : profile.metadataPointer}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
{profile.pools.length > 0 && (
<>
diff --git a/src/components/Table.tsx b/src/components/Table.tsx
index a520f07..297836c 100644
--- a/src/components/Table.tsx
+++ b/src/components/Table.tsx
@@ -15,12 +15,14 @@ const Table = ({
description,
rowsPerPage = 10,
showPagination,
+ showBorder = true,
}: {
data: TTableData;
header: string | undefined | "";
description: string | undefined | "";
rowsPerPage?: number;
showPagination?: boolean;
+ showBorder?: boolean;
}) => {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = Math.ceil(data.rows.length / rowsPerPage);
@@ -44,13 +46,11 @@ const Table = ({
const endRow = startRow + rowsPerPage;
const currentRows = data.rows.slice(startRow, endRow);
- console.log("isMobile", isMobile);
-
return (
<>
@@ -70,7 +70,13 @@ const Table = ({
)}
-
+
{!isMobile ? (
diff --git a/src/types/types.ts b/src/types/types.ts
index 3d88eba..d95b99c 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -86,3 +86,9 @@ export type TFunctionArgs = {
type: string;
value: string;
};
+
+export type TListProps = {
+ label: string;
+ value: string | React.JSX.Element;
+};
+
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index 4bfec22..90e0e67 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -1,5 +1,7 @@
+import { Metadata, MetadataProtocol } from "@/types/types";
import { getNetworks } from "./networks";
import { ethers } from "ethers";
+import { TTokenMetadata } from "@/components/Pool/types";
const networks = getNetworks();
@@ -27,11 +29,57 @@ export const convertBytesToShortString = (address: string) => {
return address.slice(0, 6) + "..." + address.slice(-4);
};
-
export const convertAddressToShortString = (address: string) => {
return address.slice(0, 6) + "..." + address.slice(-4);
};
export const copy = (data: string) => {
navigator.clipboard.writeText(data);
-};
\ No newline at end of file
+};
+
+export const amountString = (
+ amount: number,
+ tokenMetadata: TTokenMetadata,
+ chainId: number,
+): string => {
+ return (
+ ethers.formatUnits(amount, tokenMetadata.decimals ?? 18) + " " +
+ (tokenMetadata.symbol ?? getNetworks()[Number(chainId)].symbol)
+ );
+};
+
+export const fetchIpfsMetadata = async (
+ metadata: Metadata,
+): Promise