Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Community-Pools-Metrics #508

Merged
merged 17 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
EthAddress,
InfoBox,
Statistic,
DataTable,
} from "@/components";
import CancelButton from "@/components/CancelButton";
import { ConvictionBarChart } from "@/components/Charts/ConvictionBarChart";
Expand All @@ -41,7 +42,7 @@ import { ConditionObject, useDisableButtons } from "@/hooks/useDisableButtons";
import { useMetadataIpfsFetch } from "@/hooks/useIpfsFetch";
import { useSubgraphQuery } from "@/hooks/useSubgraphQuery";
import { alloABI } from "@/src/generated";
import { PoolTypes, ProposalStatus } from "@/types";
import { PoolTypes, ProposalStatus, Column } from "@/types";

import { useErrorDetails } from "@/utils/getErrorName";
import { calculatePercentageBigInt } from "@/utils/numbers";
Expand All @@ -51,6 +52,7 @@ type ProposalSupporter = {
id: string;
stakes: { amount: number }[];
};
type SupporterColumn = Column<ProposalSupporter>;

export default function Page({
params: { proposalId, garden, community: communityAddr, poolId },
Expand Down Expand Up @@ -111,10 +113,14 @@ export default function Page({
const proposalData = data?.cvproposal;
const proposalSupporters = supportersData?.members;

const filteredAndSortedProposalSupporters =
const filteredAndSortedProposalSupporters: ProposalSupporter[] =
proposalSupporters ?
proposalSupporters
.filter((item) => item.stakes && item.stakes.length > 0)
.map((item) => ({
id: item.id,
stakes: item.stakes?.map((stake) => ({ amount: stake.amount })) ?? [],
}))
.sort((a, b) => {
const maxStakeA = Math.max(
...(a.stakes ?? []).map((stake) => stake.amount),
Expand Down Expand Up @@ -415,123 +421,77 @@ export default function Page({
)}
{filteredAndSortedProposalSupporters.length > 0 && (
<ProposalSupportersTable
_proposalSupporters={
filteredAndSortedProposalSupporters as ProposalSupporter[]
}
_totalActivePoints={totalEffectiveActivePoints}
_totalStakedAmount={totalSupportPct}
_beneficiary={beneficiary}
_submitter={submitter}
supporters={filteredAndSortedProposalSupporters}
beneficiary={beneficiary}
submitter={submitter}
totalActivePoints={totalEffectiveActivePoints}
totalStakedAmount={totalSupportPct}
/>
)}
</div>
);
}

function ProposalSupportersTable({
_proposalSupporters,
_totalActivePoints,
_totalStakedAmount,
_beneficiary,
_submitter,
const ProposalSupportersTable = ({
supporters,
beneficiary,
submitter,
totalActivePoints,
totalStakedAmount,
}: {
_proposalSupporters: ProposalSupporter[];
_totalActivePoints: number;
_totalStakedAmount: number;
_beneficiary: string | undefined;
_submitter: string | undefined;
}) {
supporters: ProposalSupporter[];
beneficiary: string | undefined;
submitter: string | undefined;
totalActivePoints: number;
totalStakedAmount: number;
}) => {
const columns: SupporterColumn[] = [
{
header: supporters.length > 1 ? "Supporters" : "Supporter",
render: (supporter: ProposalSupporter) => (
<EthAddress
address={supporter.id as Address}
actions="copy"
shortenAddress={false}
icon="ens"
/>
),
},
{
header: "Role",
render: (supporter: ProposalSupporter) =>
supporter.id === beneficiary ? "Beneficiary"
: supporter.id === submitter ? "Submitter"
: "Member",
},
{
header: "Support",
render: (supporter: ProposalSupporter) =>
totalActivePoints > 0 ?
`${calculatePercentageBigInt(
BigInt(supporter?.stakes[0]?.amount),
BigInt(totalActivePoints),
)} %`
: undefined,
className: "text-center",
},
];

return (
<div className="px-2 section-layout">
<div className="sm:flex sm:items-center">
<div className="sm:flex-auto">
<h3>Supported By</h3>
<p className="mt-2 text-sm text-neutral-soft-content">
A list of all the community members that are supporting this
proposal.
<DataTable
title="Supported By"
description="A list of all the community members that are supporting this proposal."
data={supporters}
columns={columns}
footer={
//
<div className="flex justify-between py-2 border-neutral-soft-content">
<p className="subtitle">Total Support:</p>
<p className="subtitle pr-0 sm:pr-14 lg:pr-16">
{totalStakedAmount} %
</p>
</div>
</div>
<div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<table className="min-w-full divide-y divide-neutral-soft">
<thead>
<tr>
<th scope="col" className="py-3.5 pl-4 pr-3 sm:pl-0">
<h5>
{_proposalSupporters.length > 1 ?
"Supporters"
: "Supporter"}
</h5>
</th>
<th scope="col" className="px-3 py-3.5">
<h5>Role</h5>
</th>
<th scope="col" className="px-3 py-3.5 text-center">
<h5>Support</h5>
</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-soft">
{_proposalSupporters.map((supporter: ProposalSupporter) => (
<tr key={supporter.id}>
<td className="whitespace-nowrap py-5 pl-4 pr-3 sm:pl-0 text-sm text-neutral-soft-content">
<div className="flex items-center">
<div className="ml-4">
<EthAddress
address={supporter.id as Address}
actions="copy"
shortenAddress={false}
icon={"ens"}
/>
</div>
</div>
</td>
{/* members role */}
<td className="whitespace-nowrap px-3 py-5 text-sm text-neutral-soft-content">
<p>
{supporter.id === _beneficiary ?
"Beneficiary"
: supporter.id === _submitter ?
"Submitter"
: "Member"}
</p>
</td>
{/* members support */}
<td className="whitespace-nowrap px-3 py-5 text-sm text-neutral-soft-content">
<p className="subtitle">
{(_totalActivePoints ?? 0) > 0 ?
calculatePercentageBigInt(
BigInt(supporter?.stakes[0]?.amount),
BigInt(_totalActivePoints ?? 0),
)
: undefined}{" "}
%
</p>{" "}
</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<th
scope="col"
colSpan={2}
className="pl-8 pr-3 pt-4 sm:table-cell sm:pl-0"
>
<p className="subtitle">Total Support:</p>
</th>

<td className="pl-3 pr-4 pt-4 text-left sm:pr-0">
<p className="subtitle">{_totalStakedAmount} %</p>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
}
/>
);
}
};
88 changes: 87 additions & 1 deletion apps/web/app/(app)/gardens/[chain]/[garden]/[community]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
RectangleGroupIcon,
UserGroupIcon,
} from "@heroicons/react/24/outline";

import { FetchTokenResult } from "@wagmi/core";
import { Dnum, multiply } from "dnum";
import Image from "next/image";
import Link from "next/link";
Expand All @@ -28,6 +30,7 @@ import {
RegisterMember,
Statistic,
InfoWrapper,
DataTable,
} from "@/components";
import { LoadingSpinner } from "@/components/LoadingSpinner";
import MarkdownWrapper from "@/components/MarkdownWrapper";
Expand All @@ -39,14 +42,27 @@ import { useCollectQueryParams } from "@/contexts/collectQueryParams.context";
import { useDisableButtons } from "@/hooks/useDisableButtons";
import { useSubgraphQuery } from "@/hooks/useSubgraphQuery";
import { safeABI } from "@/src/generated";
import { PoolTypes } from "@/types";
import { PoolTypes, Column } from "@/types";
import { fetchIpfs } from "@/utils/ipfsUtils";
import {
parseToken,
SCALE_PRECISION,
SCALE_PRECISION_DECIMALS,
} from "@/utils/numbers";

type MembersStaked = {
memberAddress: string;
stakedTokens: string;
};

type CommunityMetricsProps = {
membersStaked: MembersStaked[] | undefined;
tokenGarden: FetchTokenResult;
communityStakedTokens: number | bigint;
};

type MemberColumn = Column<MembersStaked>;

export default function Page({
params: { chain, garden: tokenAddr, community: communityAddr },
}: {
Expand All @@ -55,6 +71,8 @@ export default function Page({
const searchParams = useCollectQueryParams();
const { address: accountAddress } = useAccount();
const [covenant, setCovenant] = useState<string | undefined>();
const [openCommDetails, setOpenCommDetails] = useState(false);

const covenantSectionRef = useRef<HTMLDivElement>(null);
const { data: tokenGarden } = useToken({
address: tokenAddr as Address,
Expand Down Expand Up @@ -276,6 +294,13 @@ export default function Page({
height={180}
width={180}
/>
<Button
onClick={() => setOpenCommDetails(!openCommDetails)}
btnStyle="outline"
className="mt-1 w-full"
>
{openCommDetails ? "Close" : "See"} Details
</Button>
</div>
<div className="flex flex-1 flex-col gap-2">
<div>
Expand Down Expand Up @@ -329,6 +354,16 @@ export default function Page({
/>
</div>
</header>

{/* <Metrics> */}
{openCommDetails && (
<CommunityDetailsTable
membersStaked={registryCommunity.members as MembersStaked[]}
tokenGarden={tokenGarden}
communityStakedTokens={communityStakedTokens}
/>
)}

<IncreasePower
memberData={isMemberResult}
registryCommunity={registryCommunity}
Expand Down Expand Up @@ -435,3 +470,54 @@ export default function Page({
</div>
);
}

const CommunityDetailsTable = ({
membersStaked,
tokenGarden,
communityStakedTokens,
}: CommunityMetricsProps) => {
const columns: MemberColumn[] = [
{
header: `Members (${membersStaked?.length})`,
render: (memberData: MembersStaked) => (
<EthAddress
address={memberData.memberAddress as Address}
actions="copy"
shortenAddress={false}
icon="ens"
/>
),
},
{
header: "Staked tokens",
render: (memberData: MembersStaked) => (
<DisplayNumber
number={[BigInt(memberData.stakedTokens), tokenGarden.decimals]}
compact={true}
tokenSymbol={tokenGarden.symbol}
/>
),
className: "flex justify-end",
},
];

return (
<DataTable
title="Community Details"
data={membersStaked as MembersStaked[]}
description="Overview of all community members and the total number of tokens they have staked."
columns={columns}
className="max-h-screen overflow-y-scroll"
footer={
<div className="flex justify-between py-2 border-neutral-soft-content">
<p className="subtitle">Total Staked:</p>
<DisplayNumber
number={[BigInt(communityStakedTokens), tokenGarden.decimals]}
compact={true}
tokenSymbol={tokenGarden.symbol}
/>
</div>
}
/>
);
};
Loading
Loading