Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
87d20ea
feat: enhance polling functionality with voting transparency on the g…
ibsule May 27, 2026
1660abd
refactor: clean up imports and improve code consistency across PollVo…
ibsule May 27, 2026
9e80ed9
feat: enhance voting components with vote stake formatting
ibsule May 27, 2026
488711c
refactor: update DesktopVoteTable to improve vote weight display
ibsule Jun 10, 2026
5f3a532
refactor: rename formatVoteStake to formatWeight for consistency acro…
ibsule Jun 10, 2026
b0ad572
feat: enhance PollVote components with additional vote data and forma…
ibsule Jun 10, 2026
28a60eb
feat: enhance PollVote components with error handling and improved vo…
ibsule Jun 18, 2026
5e9077d
fix: update sorting logic in PollVote component for mobile view
ibsule Jun 23, 2026
8601dba
fix: handle unknown voting support in MobileVoteView
ibsule Jun 23, 2026
54b8512
fix: add error handling in PollVotePopover component
ibsule Jun 23, 2026
e8aa64f
feat: enhance subgraph and PollVote components with logging and query…
ibsule Jun 23, 2026
b90a266
fix: update sorting logic in PollVote component for mobile view
ibsule Jun 23, 2026
698aac5
Merge branch 'main' into governance-vote-transparency
ECWireless Jun 23, 2026
38ad71f
refactor: optimize GraphQL queries and update PollVote component
ibsule Jun 23, 2026
dceef1d
feat: increase pagination limit in PollVotePopover component
ibsule Jun 23, 2026
26f545f
Merge branch 'governance-vote-transparency' of https://github.com/ibs…
ibsule Jun 23, 2026
d049a9c
fix: improve error handling in useGetAllVoteEvents hook
ibsule Jun 23, 2026
cdd64a7
fix: enhance loading state management in useVotes hook
ibsule Jun 29, 2026
2ffa703
Merge branch 'main' into governance-vote-transparency
ibsule Jun 29, 2026
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
77 changes: 74 additions & 3 deletions apollo/subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9681,7 +9681,7 @@ export type PollQueryVariables = Exact<{
}>;


export type PollQuery = { __typename: 'Query', poll?: { __typename: 'Poll', id: string, proposal: string, endBlock: string, quorum: string, quota: string, tally?: { __typename: 'PollTally', yes: string, no: string } | null, votes: Array<{ __typename: 'Vote', id: string }> } | null };
export type PollQuery = { __typename: 'Query', poll?: { __typename: 'Poll', id: string, proposal: string, endBlock: string, quorum: string, quota: string, tally?: { __typename: 'PollTally', yes: string, no: string } | null, votes: Array<{ __typename: 'Vote', id: string, choiceID?: PollChoice | null, voter: string, voteStake: string, nonVoteStake: string }> } | null };

export type PollsQueryVariables = Exact<{ [key: string]: never; }>;

Expand Down Expand Up @@ -9765,7 +9765,15 @@ export type VoteQueryVariables = Exact<{
}>;


export type VoteQuery = { __typename: 'Query', vote?: { __typename: 'Vote', choiceID?: PollChoice | null, voteStake: string, nonVoteStake: string } | null };
export type VoteQuery = { __typename: 'Query', vote?: { __typename: 'Vote', choiceID?: PollChoice | null, voteStake: string, nonVoteStake: string, poll?: { __typename: 'Poll', id: string, votes: Array<{ __typename: 'Vote', voteStake: string, id: string }> } | null } | null };

export type VoteEventsQueryVariables = Exact<{
first?: InputMaybe<Scalars['Int']>;
where?: InputMaybe<VoteEvent_Filter>;
}>;


export type VoteEventsQuery = { __typename: 'Query', voteEvents: Array<{ __typename: 'VoteEvent', id: string, choiceID: string, voter: string, timestamp: number, poll: { __typename: 'Poll', id: string, proposal: string }, transaction: { __typename: 'Transaction', id: string, timestamp: number } }> };


export const AccountDocument = gql`
Expand Down Expand Up @@ -10467,6 +10475,10 @@ export const PollDocument = gql`
}
votes {
id
choiceID
voter
voteStake
nonVoteStake
}
}
}
Expand Down Expand Up @@ -11117,6 +11129,13 @@ export const VoteDocument = gql`
choiceID
voteStake
nonVoteStake
poll {
id
votes {
voteStake
id
}
}
}
}
`;
Expand Down Expand Up @@ -11147,4 +11166,56 @@ export function useVoteLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<VoteQ
}
export type VoteQueryHookResult = ReturnType<typeof useVoteQuery>;
export type VoteLazyQueryHookResult = ReturnType<typeof useVoteLazyQuery>;
export type VoteQueryResult = Apollo.QueryResult<VoteQuery, VoteQueryVariables>;
export type VoteQueryResult = Apollo.QueryResult<VoteQuery, VoteQueryVariables>;
export const VoteEventsDocument = gql`
query voteEvents($first: Int, $where: VoteEvent_filter) {
voteEvents(
orderBy: timestamp
orderDirection: desc
first: $first
where: $where
) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
id
choiceID
voter
timestamp
poll {
id
proposal
}
transaction {
id
timestamp
}
}
}
`;

/**
* __useVoteEventsQuery__
*
* To run a query within a React component, call `useVoteEventsQuery` and pass it any options that fit your needs.
* When your component renders, `useVoteEventsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useVoteEventsQuery({
* variables: {
* first: // value for 'first'
* where: // value for 'where'
* },
* });
*/
export function useVoteEventsQuery(baseOptions?: Apollo.QueryHookOptions<VoteEventsQuery, VoteEventsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<VoteEventsQuery, VoteEventsQueryVariables>(VoteEventsDocument, options);
}
export function useVoteEventsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<VoteEventsQuery, VoteEventsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<VoteEventsQuery, VoteEventsQueryVariables>(VoteEventsDocument, options);
}
export type VoteEventsQueryHookResult = ReturnType<typeof useVoteEventsQuery>;
export type VoteEventsLazyQueryHookResult = ReturnType<typeof useVoteEventsLazyQuery>;
export type VoteEventsQueryResult = Apollo.QueryResult<VoteEventsQuery, VoteEventsQueryVariables>;
221 changes: 221 additions & 0 deletions components/PollVote/DesktopVoteTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import EthAddressBadge from "@components/EthAddressBadge";
import { ExplorerTooltip } from "@components/ExplorerTooltip";
import DataTable from "@components/Table";
import TransactionBadge from "@components/TransactionBadge";
import { VOTING_SUPPORT_MAP } from "@lib/api/types/votes";
import { Badge, Box, Text } from "@livepeer/design-system";
import { CounterClockwiseClockIcon } from "@radix-ui/react-icons";
import dayjs from "dayjs";
import React, { useMemo } from "react";
import { Column } from "react-table";

import { PollVoteType } from ".";

export interface PollVoteTableProps {
votes: PollVoteType[];
onSelect: (voter: {
address: string;
voteStake: string;
ensName?: string;
}) => void;
pageSize?: number;
totalPages?: number;
currentPage?: number;
onPageChange?: (page: number) => void;
formatWeight: (stake: string) => string;
}

export const DesktopVoteTable: React.FC<PollVoteTableProps> = ({
votes,
onSelect,
pageSize = 10,
formatWeight,
}) => {
const columns = useMemo<Column<PollVoteType>[]>(
() => [
{
Header: "Voter",
accessor: "ensName",
id: "voter",
Cell: ({ row }) => (
<Box css={{ minWidth: 120 }}>
<EthAddressBadge value={row.original.voter} />
</Box>
),
},
{
Header: "Support",
accessor: "choiceID",
id: "support",
Cell: ({ row }) => {
const support =
VOTING_SUPPORT_MAP[row.original.choiceID] ||
VOTING_SUPPORT_MAP["Unknown"];

return (
<Box css={{ minWidth: 100 }}>
<Badge
size="1"
css={{
backgroundColor: support.style.backgroundColor,
color: support.style.color,
fontWeight: support.style.fontWeight,
border: "none",
width: "86px",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
gap: "$1",
}}
>
<Box as={support.icon} css={{ width: 12, height: 12 }} />
{support.text}
</Badge>
</Box>
);
},
},
{
Header: "Weight",
accessor: "voteStake",
id: "weight",
Comment thread
ibsule marked this conversation as resolved.
Cell: ({ row }) => (
<Box css={{ minWidth: 140 }}>
<Text
css={{
fontWeight: 600,
color: "$hiContrast",
whiteSpace: "nowrap",
}}
size="2"
>
{formatWeight(row.original.voteStake)}
</Text>
</Box>
),
sortType: (rowA, rowB) => {
return (
parseFloat(rowA.original.voteStake) -
parseFloat(rowB.original.voteStake)
);
},
},
{
Header: "Timestamp",
accessor: "timestamp",
id: "timestamp",
Cell: ({ row }) => {
if (row.original.timestamp) {
return (
<Text size="1" css={{ color: "$neutral11" }}>
{dayjs
.unix(row.original.timestamp)
.format("MMM D YYYY, h:mm a")}
</Text>
);
} else {
return (
<Text size="1" css={{ color: "$neutral9" }}>
N/A
</Text>
);
}
},
},
{
Header: "Transaction",
accessor: "transactionHash",
id: "transaction",
Cell: ({ row }) => (
<Box
css={{
minWidth: 130,
display: "flex",
alignItems: "center",
height: "100%",
}}
>
{row.original.transactionHash ? (
<TransactionBadge id={row.original.transactionHash} />
) : (
<Text css={{ color: "$neutral9" }} size="2">
N/A
</Text>
)}
</Box>
),
},
{
Header: "",
id: "history",
Cell: ({ row }) => (
<Box
css={{
minWidth: 40,
textAlign: "right",
color: "$primary11",
paddingRight: "$2",
}}
>
<ExplorerTooltip content="See their voting history">
<Box
as="button"
onClick={(e) => {
e.stopPropagation();
onSelect({
address: row.original.voter,
ensName: row.original.ensName,
voteStake: row.original.voteStake,
});
}}
css={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: 32,
height: 32,
borderRadius: "50%",
cursor: "pointer",
border: "none",
backgroundColor: "transparent",
color: "$neutral10",
"&:hover": {
color: "$primary11",
backgroundColor: "$primary3",
transform: "rotate(-15deg)",
},
transition: "color .2s, background-color .2s, transform .2s",
}}
>
<Box
as={CounterClockwiseClockIcon}
css={{ width: 16, height: 16 }}
/>
</Box>
</ExplorerTooltip>
</Box>
),
disableSortBy: true,
},
],
[formatWeight, onSelect]
);

return (
<Box css={{ display: "none", "@bp2": { display: "block" } }}>
<DataTable
data={votes}
columns={columns}
initialState={{
pageSize,
sortBy: [
{
id: "weight",
desc: true,
},
],
}}
/>
</Box>
);
};
57 changes: 57 additions & 0 deletions components/PollVote/MobileVoteCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Pagination from "@components/Table/Pagination";
import { Box, Text } from "@livepeer/design-system";
import React from "react";

import { PollVoteTableProps } from "./DesktopVoteTable";
import { MobileVoteView } from "./MobileVoteView";

export const MobileVoteCards: React.FC<PollVoteTableProps> = (props) => {
const {
votes,
onSelect,
totalPages = 0,
currentPage = 1,
formatWeight,
onPageChange,
} = props;

return (
<Box css={{ display: "block", "@bp2": { display: "none" } }}>
<Text
css={{
textAlign: "center",
fontSize: "$2",
color: "$neutral11",
marginTop: "$2",
marginBottom: "$3",
}}
>
View a voter&apos;s proposal voting history by clicking the history
icon.
</Text>

{votes.map((vote) => {
return (
<MobileVoteView
key={vote.transactionHash || vote.voter}
vote={vote}
formatWeight={formatWeight}
onSelect={onSelect}
/>
);
})}

{/* Pagination */}
{totalPages > 1 && (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
canPrevious={currentPage > 1}
canNext={currentPage < totalPages}
onPrevious={() => onPageChange?.(currentPage - 1)}
onNext={() => onPageChange?.(currentPage + 1)}
/>
)}
</Box>
);
};
Loading
Loading