Skip to content

Commit

Permalink
feat: add vote type to voted label
Browse files Browse the repository at this point in the history
Closes #1054.
  • Loading branch information
emccorson committed Aug 30, 2024
1 parent 5901f3e commit abf429f
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 70 deletions.
16 changes: 10 additions & 6 deletions apps/namadillo/src/App/Governance/GovernanceOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Panel, SkeletonLoading } from "@namada/components";
import { ConnectBanner } from "App/Common/ConnectBanner";
import { PageWithSidebar } from "App/Common/PageWithSidebar";
import { allProposalsAtom, votedProposalIdsAtom } from "atoms/proposals";
import { allProposalsAtom, votedProposalsAtom } from "atoms/proposals";
import { namadaExtensionConnectedAtom } from "atoms/settings";
import {
atomsAreFetching,
Expand All @@ -18,11 +18,11 @@ import { UpcomingProposals } from "./UpcomingProposals";
export const GovernanceOverview: React.FC = () => {
const isConnected = useAtomValue(namadaExtensionConnectedAtom);
const allProposals = useAtomValue(allProposalsAtom);
const votedProposalIds = useAtomValue(votedProposalIdsAtom);
const votedProposals = useAtomValue(votedProposalsAtom);

// TODO: is there a better way than this to show that votedProposalIdsAtom
// is dependent on isConnected?
const extensionAtoms = isConnected ? [votedProposalIds] : [];
const extensionAtoms = isConnected ? [votedProposals] : [];
const activeAtoms = [allProposals, ...extensionAtoms];

const liveProposals =
Expand All @@ -35,7 +35,7 @@ export const GovernanceOverview: React.FC = () => {

useNotifyOnAtomError(activeAtoms, [
allProposals.isError,
votedProposalIds.isError,
votedProposals.isError,
]);

return (
Expand All @@ -53,7 +53,7 @@ export const GovernanceOverview: React.FC = () => {
>
<LiveGovernanceProposals
proposals={liveProposals}
votedProposalIds={votedProposalIds.data! || []}
votedProposals={votedProposals.data || []}
/>
</ProposalListPanel>
<ProposalListPanel
Expand All @@ -70,7 +70,11 @@ export const GovernanceOverview: React.FC = () => {
errorText="Unable to load the list of proposals"
atoms={activeAtoms}
>
<AllProposalsTable votedProposalIds={votedProposalIds.data! || []} />
<AllProposalsTable
votedProposalIds={(votedProposals.data || []).map(
(v) => v.proposalId
)}
/>
</ProposalListPanel>
</div>
<aside className="flex flex-col gap-2 mt-1.5 lg:mt-0">
Expand Down
20 changes: 11 additions & 9 deletions apps/namadillo/src/App/Governance/LiveGovernanceProposals.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SegmentedBar, Stack } from "@namada/components";
import { Proposal, voteTypes } from "@namada/types";
import { Proposal, VoteType, voteTypes } from "@namada/types";
import BigNumber from "bignumber.js";
import clsx from "clsx";
import { GoInfo } from "react-icons/go";
Expand All @@ -11,8 +11,8 @@ import { colors } from "./types";

const ProposalListItem: React.FC<{
proposal: Proposal;
voted: boolean;
}> = ({ proposal, voted }) => {
vote?: VoteType;
}> = ({ proposal, vote }) => {
const { status } = proposal;

const navigate = useNavigate();
Expand Down Expand Up @@ -50,7 +50,9 @@ const ProposalListItem: React.FC<{
<div className="flex-1">{proposal.content.title}</div>
<TypeLabel proposalType={proposal.proposalType} color="dark" />
<div className="min-w-20">
{voted && <VotedLabel className="text-[10px] w-fit m-auto" />}
{typeof vote !== "undefined" && (
<VotedLabel vote={vote} className="text-[10px] w-fit m-auto" />
)}
</div>
<i
className={clsx(
Expand All @@ -68,14 +70,13 @@ const ProposalListItem: React.FC<{
};

type LiveGovernanceProposalsProps = {
votedProposalIds: bigint[];
} & {
proposals: Proposal[];
votedProposals: { proposalId: bigint; vote: VoteType }[];
};

export const LiveGovernanceProposals: React.FC<
LiveGovernanceProposalsProps
> = ({ proposals, votedProposalIds }) => {
> = ({ proposals, votedProposals }) => {
return (
<div className="max-h-[490px] flex flex-col">
<Stack
Expand All @@ -84,11 +85,12 @@ export const LiveGovernanceProposals: React.FC<
className="dark-scrollbar overscroll-contain overflow-x-auto"
>
{proposals.map((proposal) => {
const voted = votedProposalIds.includes(proposal.id);
const vote = votedProposals.find((v) => v.proposalId === proposal.id)
?.vote;
return (
<ProposalListItem
proposal={proposal}
voted={voted}
vote={vote}
key={proposal.id.toString()}
/>
);
Expand Down
34 changes: 19 additions & 15 deletions apps/namadillo/src/App/Governance/ProposalHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import {
ProgressBar as ProgressBarComponent,
Stack,
} from "@namada/components";
import { Proposal } from "@namada/types";
import { Proposal, VoteType } from "@namada/types";
import {
canVoteAtom,
proposalFamily,
proposalVotedFamily,
proposalVoteFamily,
} from "atoms/proposals";
import { namadaExtensionConnectedAtom } from "atoms/settings";
import clsx from "clsx";
Expand All @@ -33,7 +33,7 @@ export const ProposalHeader: React.FC<{
proposalId: bigint;
}> = ({ proposalId }) => {
const proposal = useAtomValue(proposalFamily(proposalId));
const voted = useAtomValue(proposalVotedFamily(proposalId));
const vote = useAtomValue(proposalVoteFamily(proposalId));

return (
<>
Expand All @@ -54,11 +54,11 @@ export const ProposalHeader: React.FC<{
<hr className="border-neutral-900 w-full mb-4" />
<div className="flex gap-2 mb-4">
<StatusLabel proposal={proposal} />
<VotedLabel voted={voted} />
<VotedLabel vote={vote} />
</div>
<div className="flex gap-10 bg-neutral-900 mb-9 px-5 py-3 -mx-3 rounded-md">
<Progress proposal={proposal} />
<VoteButton proposal={proposal} voted={voted} proposalId={proposalId} />
<VoteButton proposal={proposal} vote={vote} proposalId={proposalId} />
</div>
</>
);
Expand Down Expand Up @@ -291,9 +291,9 @@ const ProgressBar: React.FC<{

const VoteButton: React.FC<{
proposal: AtomWithQueryResult<Proposal>;
voted: boolean | undefined;
vote: AtomWithQueryResult<VoteType | undefined>;
proposalId: bigint;
}> = ({ proposal, voted, proposalId }) => {
}> = ({ proposal, vote, proposalId }) => {
const navigate = useNavigate();
const isExtensionConnected = useAtomValue(namadaExtensionConnectedAtom);
const canVote = useAtomValue(
Expand All @@ -305,11 +305,7 @@ const VoteButton: React.FC<{
}

const { disabled, onClick, text } = (() => {
if (
proposal.status === "pending" ||
proposal.status === "error" ||
typeof voted === "undefined"
) {
if (!proposal.isSuccess || !vote.isSuccess) {
return {
disabled: true,
onClick: undefined,
Expand All @@ -321,6 +317,7 @@ const VoteButton: React.FC<{
const disabled =
!isExtensionConnected || !canVote.data || status !== "ongoing";

const voted = typeof vote.data !== "undefined";
const text = voted ? "Edit Vote" : "Vote";

return {
Expand All @@ -347,6 +344,13 @@ const VoteButton: React.FC<{
};

const VotedLabel: React.FC<{
voted: boolean | undefined;
}> = ({ voted }) =>
voted ? <VotedLabelComponent className="text-xs min-w-22" /> : null;
vote: AtomWithQueryResult<VoteType | undefined>;
}> = ({ vote }) => {
if (vote.isSuccess && typeof vote.data !== "undefined") {
return (
<VotedLabelComponent vote={vote.data} className="text-xs min-w-22" />
);
} else {
return null;
}
};
35 changes: 24 additions & 11 deletions apps/namadillo/src/App/Governance/ProposalLabels.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { InsetLabel, RoundedLabel } from "@namada/components";
import { ProposalStatus, ProposalType } from "@namada/types";
import { ProposalStatus, ProposalType, VoteType } from "@namada/types";
import { assertNever } from "@namada/utils";
import { GoCheckCircleFill } from "react-icons/go";
import { GoCheckCircleFill, GoSkipFill, GoXCircleFill } from "react-icons/go";
import { twMerge } from "tailwind-merge";
import { proposalStatusToString, proposalTypeStringToString } from "utils";

Expand All @@ -24,22 +24,35 @@ export const StatusLabel: React.FC<
);
};

export const VotedLabel: React.FC<React.ComponentProps<"div">> = ({
type VotedLabelProps = {
vote: VoteType;
} & React.ComponentProps<"div">;

export const VotedLabel: React.FC<VotedLabelProps> = ({
vote,
className,
...props
}) => {
const { icon, label, color } =
vote === "yay" ?
{ icon: <GoCheckCircleFill />, label: "yes", color: "text-yay" }
: vote === "nay" ?
{ icon: <GoXCircleFill />, label: "no", color: "text-nay" }
: vote === "abstain" ?
{
icon: <GoSkipFill className="rotate-45" />,
label: "abstain",
color: "text-abstain",
}
: assertNever(vote);

return (
<RoundedLabel
{...props}
className={twMerge(
"flex gap-1 text-cyan py-1 px-2 items-center",
className
)}
className={twMerge("flex gap-1 py-1 px-2 items-center", color, className)}
>
<div className="grow">Voted</div>
<i className="inline-flex text-lg">
<GoCheckCircleFill />
</i>
<div className="grow">Voted {label}</div>
<i className="inline-flex text-lg">{icon}</i>
</RoundedLabel>
);
};
Expand Down
28 changes: 14 additions & 14 deletions apps/namadillo/src/atoms/proposals/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { defaultAccountAtom } from "atoms/accounts";
import { indexerApiAtom } from "atoms/api";
import { chainAtom } from "atoms/chain";
import { queryDependentFn } from "atoms/utils";
import { atom } from "jotai";
import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query";
import { atomFamily } from "jotai/utils";
import { TransactionPair } from "lib/query";
Expand All @@ -18,11 +17,12 @@ import {
fetchAllProposals,
fetchPaginatedProposals,
fetchProposalById,
fetchVotedProposalIds,
fetchVotedProposalsByAccount,
} from "./functions";

import { Bond as NamadaIndexerBond } from "@anomaorg/namada-indexer-client";
import { shouldUpdateProposalAtom } from "atoms/etc";

export const proposalFamily = atomFamily((id: bigint) =>
atomWithQuery((get) => {
const api = get(indexerApiAtom);
Expand All @@ -37,16 +37,16 @@ export const proposalFamily = atomFamily((id: bigint) =>
})
);

export const proposalVotedFamily = atomFamily((id: bigint) =>
atom<boolean | undefined>((get) => {
const query = get(votedProposalIdsAtom);
export const proposalVoteFamily = atomFamily((id: bigint) =>
atomWithQuery<VoteType | undefined>((get) => {
const votedProposals = get(votedProposalsAtom);

if (query.status === "pending" || query.status === "error") {
return undefined;
} else {
const votedProposalIds = query.data;
return votedProposalIds.includes(id);
}
return {
queryKey: ["proposal-vote", id.toString()],
...queryDependentFn(async () => {
return votedProposals.data!.find((v) => v.proposalId === id)?.vote;
}, [votedProposals]),
};
})
);

Expand Down Expand Up @@ -94,20 +94,20 @@ export const paginatedProposalsFamily = atomFamily(
a?.search === b?.search
);

export const votedProposalIdsAtom = atomWithQuery((get) => {
export const votedProposalsAtom = atomWithQuery((get) => {
const account = get(defaultAccountAtom);
const api = get(indexerApiAtom);
const enablePolling = get(shouldUpdateProposalAtom);

return {
// TODO: subscribe to indexer events when it's done
refetchInterval: enablePolling ? 1000 : false,
queryKey: ["voted-proposal-ids", account.data],
queryKey: ["voted-proposals", account.data],
...queryDependentFn(async () => {
if (typeof account.data === "undefined") {
throw new Error("no account found");
}
return await fetchVotedProposalIds(api, account.data);
return await fetchVotedProposalsByAccount(api, account.data);
}, [account]),
};
});
Expand Down
19 changes: 6 additions & 13 deletions apps/namadillo/src/atoms/proposals/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,23 +358,16 @@ export const fetchProposalVotes = async (
return votes;
};

export const fetchProposalVoted = async (
export const fetchVotedProposalsByAccount = async (
api: DefaultApi,
id: bigint,
account: Account
): Promise<boolean> => {
const votes = await fetchProposalVotes(api, id);
return votes.some((vote) => vote.address === account.address);
};

export const fetchVotedProposalIds = async (
api: DefaultApi,
account: Account
): Promise<bigint[]> => {
): Promise<{ proposalId: bigint; vote: VoteType }[]> => {
const response = await api.apiV1GovVoterAddressVotesGet(account.address);
const proposalIds = response.data.map((vote) => BigInt(vote.proposalId));

return proposalIds;
return response.data.map(({ proposalId, vote }) => ({
proposalId: BigInt(proposalId),
vote,
}));
};

export const createVoteProposalTx = async (
Expand Down
4 changes: 2 additions & 2 deletions apps/namadillo/src/atoms/syncStatus/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { accountBalanceAtom } from "atoms/accounts/atoms";
import { allProposalsAtom, votedProposalIdsAtom } from "atoms/proposals/atoms";
import { allProposalsAtom, votedProposalsAtom } from "atoms/proposals/atoms";
import { indexerHeartbeatAtom, rpcHeartbeatAtom } from "atoms/settings/atoms";
import { allValidatorsAtom, myValidatorsAtom } from "atoms/validators/atoms";
import { atom } from "jotai";
Expand All @@ -17,7 +17,7 @@ export const syncStatusAtom = atom((get) => {

// Governance
get(allProposalsAtom),
get(votedProposalIdsAtom),
get(votedProposalsAtom),
];

const isSyncing = queries.some((q) => q.isFetching);
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export const colors = {
rblack: "#000000",
upcoming: "#7DA0A8",
transparent: "#00000000",
yay: "#15DD89",
nay: "#DD1599",
abstain: "#8A8A8A",

yellow: {
DEFAULT: "#FFFF00",
Expand Down

0 comments on commit abf429f

Please sign in to comment.