diff --git a/components/OrchestratorVotingList/index.tsx b/components/OrchestratorVotingList/index.tsx
new file mode 100644
index 00000000..84de0bdd
--- /dev/null
+++ b/components/OrchestratorVotingList/index.tsx
@@ -0,0 +1,314 @@
+import Table from "@components/Table";
+import { textTruncate } from "@lib/utils";
+import { Badge, Box, Flex, Link as A, Text } from "@livepeer/design-system";
+import { CheckIcon, Cross2Icon, MinusIcon } from "@radix-ui/react-icons";
+import Link from "next/link";
+import numeral from "numeral";
+import QRCode from "qrcode.react";
+import { useMemo } from "react";
+
+import { ExplorerTooltip } from "@components/ExplorerTooltip";
+import { useEnsData } from "hooks";
+
+
+
+type VoterSummary = {
+ id: string;
+ noOfProposalsVotedOn: number;
+ noOfVotesCasted: number;
+ mostRecentVotes: (string | null)[];
+ votingTurnout: number;
+};
+
+const OrchestratorVotingList = ({ initialVoterData, pageSize = 10 }: { initialVoterData?: VoterSummary[], pageSize: number }) => {
+
+ const columns = useMemo(
+ () => [
+ {
+ Header: (
+
+ The account which is actively coordinating transcoders and
+ receiving fees/rewards.
+
+ }
+ >
+
+ Orchestrator
+
+
+ ),
+ accessor: "id",
+ Cell: ({ row }) => {
+ const identity = useEnsData(row.values.id);
+
+ return (
+
+
+
+
+ {+row.id + 1}
+
+
+
+ {identity?.avatar ? (
+
+ ) : (
+
+ )}
+ {identity?.name ? (
+
+
+ {textTruncate(identity.name, 20, "…")}
+
+
+ {row.values.id.substring(0, 6)}
+
+
+ ) : (
+
+ {row.values.id.replace(row.values.id.slice(7, 37), "…")}
+
+ )}
+
+
+
+
+ );
+ },
+ },
+ {
+ Header: (
+
+ The total number of governance proposals this orchestrator has participated in by casting a vote.
+
+ }
+ >
+
+ Number of Proposals Voted On
+
+
+
+ ),
+ accessor: "noOfProposalsVotedOn",
+ Cell: ({ row }) => (
+
+
+ {numeral(row.values.noOfProposalsVotedOn).format("0,0")}
+
+
+ ),
+ sortType: "number",
+ },
+ {
+ Header: (
+
+ The total count of individual votes submitted by this orchestrator across all proposals.
+
+ }
+ >
+
+ Number of Votes Casted
+
+
+ ),
+ accessor: "noOfVotesCasted",
+ Cell: ({ row }) => (
+
+
+ {numeral(row.values.noOfVotesCasted).format("0,0")}
+
+
+ ),
+ sortType: "number",
+ },
+ {
+ Header: (
+
+ A list of up to 5 of the orchestrator’s most recent votes, marked as [✓] for For, [✗] for Against, and [–] for Abstain.
+
+ }
+ >
+
+ Most Recent Votes
+
+
+ ),
+ accessor: "mostRecentVotes",
+ Cell: ({ row }) => (
+
+
+ {row.values.mostRecentVotes?.map((mostRecentVote, index) => {
+
+ let icon =
+ mostRecentVote == "for" ? (
+
+ ) : mostRecentVote == "against" ? (
+
+ ) : mostRecentVote == "abstain" ? (
+
+ ) : null;
+
+ return (
+
+ {icon}
+
+ );
+ })}
+
+
+ ),
+ },
+ {
+ Header: (
+
+ The percentage of total governance proposals this orchestrator voted on, showing how actively they participate in protocol decisions.
+
+ }
+ >
+
+ Voting Turnout
+
+
+
+ ),
+ accessor: "votingTurnout",
+ Cell: ({ row }) => (
+
+
+ {numeral(row.values.votingTurnout).format("0.0%")}
+
+
+ ),
+ sortType: "number",
+ },
+ ],
+ []
+ );
+ if (initialVoterData) {
+ return (
+
+ );
+ } else {
+ return null;
+ }
+};
+
+
+
+export default OrchestratorVotingList;
diff --git a/components/VotingHistoryView/index.tsx b/components/VotingHistoryView/index.tsx
new file mode 100644
index 00000000..91eac400
--- /dev/null
+++ b/components/VotingHistoryView/index.tsx
@@ -0,0 +1,239 @@
+import {
+ Card as CardBase,
+ Link as A,
+ styled,
+ Flex,
+ Box,
+} from "@livepeer/design-system";
+import { CUBE_TYPE, getCubeData } from "cube/cube-client";
+import { getAccountVotingHistory } from "cube/queryGenrator";
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
+import { QuestionMarkCircledIcon } from "@radix-ui/react-icons";
+import Spinner from "@components/Spinner";
+import Image from "next/image";
+import { ExplorerTooltip } from "@components/ExplorerTooltip";
+
+
+
+const Index = () => {
+ const router = useRouter();
+ const query = router.query;
+ const account = query.account as string;
+
+ const [proposalVotedOn, setProposalVotedOn] = useState();
+ const [votingTurnOut, setVotingTurnOut] = useState();
+ const [votingData, setVotingData] = useState()
+ const [isLoading, setIsLoading] = useState(false);
+
+ const getBackgroundColorByStatus = (status: string) => {
+ let bgColor = "#212322";
+ switch (status) {
+ case "Active":
+ bgColor = "#16271F"
+ break;
+ case "Defeated":
+ bgColor = "#321C1D";
+ break;
+ case "Executed":
+ bgColor = "#212322";
+ break;
+ default:
+ break;
+ }
+ return bgColor;
+ }
+
+ const getTextStyleByStatus = (status: string) => {
+ const stylesMap: Record = {
+ Active: {
+ color: "#51A7FD",
+ backgroundColor: "#11233E",
+ maxWidth: 80,
+ justifyContent: 'center',
+ display: 'flex',
+ borderRadius: 8
+ },
+ Defeated: {
+ color: "#FF6468",
+ backgroundColor: "#3C181A",
+ maxWidth: 80,
+ justifyContent: 'center',
+ display: 'flex',
+ borderRadius: 8
+ },
+ Executed: {
+ color: "#4ABF87",
+ backgroundColor: "#10291E",
+ maxWidth: 80,
+ justifyContent: 'center',
+ display: 'flex',
+ borderRadius: 8
+ },
+ };
+
+ return stylesMap[status] || {}; // Returns styles if status is found, otherwise returns an empty object
+ };
+
+
+
+ function shortenAddress(address: string) {
+ if (address.length < 10) return address; // Handle short addresses
+
+ const first = address.slice(0, 6); // Get the '0x' + first 4 characters
+ const last = address.slice(-4); // Get last 4 characters
+
+ return `${first}...${last}`; // Return formatted string
+ }
+
+
+
+ const fetchingData = async () => {
+ setIsLoading(true);
+ try {
+ const query = getAccountVotingHistory(account);
+ const response = await getCubeData(query, { type: CUBE_TYPE.SERVER });
+ const data = response[0].data;
+ if (data.length > 0) {
+ setVotingTurnOut(data[0]['LivepeerProposalStatus.votingTurnout']);
+ setProposalVotedOn(data[0]["LivepeerProposalStatus.proposalVotedOn"]);
+ setVotingData(data);
+ }
+ setIsLoading(false)
+ } catch (error) {
+ setIsLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ fetchingData();
+ }, [])
+
+ const getDateTimeAndRound = (date: string, round: string): string => {
+ // Parse the date string to a Date object
+ const dateObj = new Date(date);
+
+ // Function to format the date to "MM/DD/YYYY h:mm:ss a"
+ const formatDate = (date: Date): string => {
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ const hours = date.getHours();
+ const minutes = date.getMinutes();
+ const seconds = date.getSeconds();
+ const ampm = hours >= 12 ? 'pm' : 'am';
+
+ const day = date.getDate();
+ const month = months[date.getMonth()];
+ const year = date.getFullYear();
+ const formattedTime = `${month} ${day}, ${year} ${hours % 12 || 12}:${minutes < 10 ? '0' + minutes : minutes}:${seconds < 10 ? '0' + seconds : seconds} ${ampm}`;
+
+ return formattedTime;
+ };
+
+ // Round logic (In case the round value needs transformation, it's done here)
+ const roundNumber = round.split("-")[0]; // Assuming round is in the format "3466-01-01T00:00:00.000", just using the first part
+
+ // Format date
+ const formattedDate = formatDate(dateObj);
+
+ // Return the final output in the required format
+ return `${formattedDate} - Round #${roundNumber}`;
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+
+
+
+
+
PROPOSALS VOTED ON
+
+ The total number of governance proposals this orchestrator has participated in by casting a vote.
+
+ }
+ >
+
+
+
+
+
+
{proposalVotedOn}
+
+
+
+
+
VOTING TURNOUT
+
+ The percentage of total governance proposals this orchestrator voted on, showing how actively they participate in protocol decisions.
+
+ }
+ >
+
+
+
+
+