Skip to content

Commit

Permalink
feat: add result page
Browse files Browse the repository at this point in the history
  • Loading branch information
kittybest committed Nov 13, 2024
1 parent 25af435 commit d3b0a76
Show file tree
Hide file tree
Showing 36 changed files with 328 additions and 239 deletions.
5 changes: 0 additions & 5 deletions packages/coordinator/scripts/uploadRoundMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export interface RoundMetadata {
registrationEndsAt: Date;
votingStartsAt: Date;
votingEndsAt: Date;
tallyFile: string;
}

interface IUploadMetadataProps {
Expand Down Expand Up @@ -137,17 +136,13 @@ export async function collectMetadata(): Promise<RoundMetadata> {

rl.close();

// NOTICE! this is when you use vercel blob storage, if you're using another tool, please change this part.
const vercelStoragePrefix = `https://${process.env.BLOB_READ_WRITE_TOKEN?.split("_")[3]}.public.blob.vercel-storage.com`;

return {
roundId,
description,
startsAt,
registrationEndsAt: new Date(startsAt.getTime() + registrationEndsIn * 1000),
votingStartsAt: new Date(startsAt.getTime() + registrationEndsIn * 1000),
votingEndsAt: new Date(startsAt.getTime() + registrationEndsIn * 1000 + votingEndsIn * 1000),
tallyFile: `${vercelStoragePrefix}/tally-${roundId}.json`,
};
}

Expand Down
5 changes: 5 additions & 0 deletions packages/interface/public/bronze.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/interface/public/gold.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/interface/public/line-chart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/interface/public/silver.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/interface/src/components/BallotOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const BallotOverview = ({ title = undefined, pollId }: IBallotOverviewPro

const ballot = useMemo(() => getBallot(pollId), [pollId, getBallot]);

const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

return (
<Link
Expand Down
2 changes: 1 addition & 1 deletion packages/interface/src/components/EligibilityDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const EligibilityDialog = ({ pollId = "" }: IEligibilityDialogProps): JSX
useMaci();
const router = useRouter();

const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

const votingEndsAt = useMemo(() => {
const round = getRoundByPollId(pollId);
Expand Down
2 changes: 1 addition & 1 deletion packages/interface/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Header = ({ navLinks, pollId = "" }: IHeaderProps) => {
const { asPath } = useRouter();
const [isOpen, setOpen] = useState(false);
const { getBallot } = useBallot();
const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });
const { theme, setTheme } = useTheme();

const ballot = useMemo(() => getBallot(pollId), [pollId, getBallot]);
Expand Down
2 changes: 1 addition & 1 deletion packages/interface/src/components/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const Info = ({
showAppState = false,
showBallot = false,
}: IInfoProps): JSX.Element => {
const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

const { getRoundByPollId } = useRound();
const round = getRoundByPollId(pollId);
Expand Down
27 changes: 23 additions & 4 deletions packages/interface/src/contexts/Round.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { config } from "~/config";
import { api } from "~/utils/api";

import type { RoundContextType, RoundProviderProps } from "./types";
import type { IRoundData } from "~/utils/types";
import type { IRoundData, Tally } from "~/utils/types";

export const RoundContext = createContext<RoundContextType | undefined>(undefined);

export const RoundProvider: React.FC<RoundProviderProps> = ({ children }: RoundProviderProps) => {
const [isLoading, setIsLoading] = useState<boolean>(false);

const polls = api.maci.poll.useQuery(undefined, { enabled: Boolean(config.maciSubgraphUrl) });
const rounds = api.maci.round.useQuery({ polls: polls.data ?? [] }, { enabled: Boolean(polls.data) });
const polls = api.maci.polls.useQuery(undefined, { enabled: Boolean(config.maciSubgraphUrl) });
const rounds = api.maci.rounds.useQuery({ polls: polls.data ?? [] }, { enabled: Boolean(polls.data) });
const tallies = api.maci.tallies.useQuery(undefined, { enabled: Boolean(config.maciSubgraphUrl) });

// on load we fetch the data from the poll
useEffect(() => {
Expand All @@ -28,6 +29,15 @@ export const RoundProvider: React.FC<RoundProviderProps> = ({ children }: RoundP
rounds.refetch().catch(console.error);
}, [polls, rounds]);

useEffect(() => {
if (tallies.data) {
return;
}

// eslint-disable-next-line no-console
tallies.refetch().catch(console.error);
}, [tallies]);

const getRoundByRoundId = useCallback(
(roundId: string): IRoundData | undefined => rounds.data?.find((round) => round.roundId === roundId),
[rounds],
Expand All @@ -38,14 +48,23 @@ export const RoundProvider: React.FC<RoundProviderProps> = ({ children }: RoundP
[rounds],
);

const isRoundTallied = useCallback(
(tallyAddress: string): boolean => {
const t = tallies.data?.find((tally: Tally) => tally.id === tallyAddress);
return !!t && t.results.length > 0;
},
[tallies],
);

const value = useMemo(
() => ({
rounds: rounds.data,
getRoundByRoundId,
getRoundByPollId,
isLoading,
isRoundTallied,
}),
[rounds, getRoundByRoundId, getRoundByPollId, isLoading],
[rounds, getRoundByRoundId, getRoundByPollId, isLoading, isRoundTallied],
);

return <RoundContext.Provider value={value as RoundContextType}>{children}</RoundContext.Provider>;
Expand Down
1 change: 1 addition & 0 deletions packages/interface/src/contexts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface RoundContextType {
getRoundByRoundId: (roundId: string) => IRoundData | undefined;
getRoundByPollId: (pollId: string) => IRoundData | undefined;
isLoading: boolean;
isRoundTallied: (tallyAddress: string) => boolean;
}

export interface RoundProviderProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const getServerSideProps: GetServerSideProps = async ({ query: { pollId }

export const BallotConfirmation = ({ pollId }: IBallotConfirmationProps): JSX.Element => {
const { getBallot, sumBallot } = useBallot();
const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });
const { getRoundByPollId } = useRound();
const round = useMemo(() => getRoundByPollId(pollId), [pollId, getRoundByPollId]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { ZeroAddress } from "ethers";
import { useMemo } from "react";
import { Hex } from "viem";

import { Button } from "~/components/ui/Button";
import { config } from "~/config";
import { useRound } from "~/contexts/Round";
import { useProjectResults } from "~/hooks/useResults";
import { formatNumber } from "~/utils/formatNumber";

export interface IProjectAwardedProps {
pollId: string;
tallyFile?: string;
registryAddress: string;
id?: string;
}

export const ProjectAwarded = ({
pollId,
tallyFile = undefined,
registryAddress,
id = "",
}: IProjectAwardedProps): JSX.Element | null => {
const amount = useProjectResults(id, registryAddress as Hex, pollId, tallyFile);
export const ProjectAwarded = ({ pollId, registryAddress, id = "" }: IProjectAwardedProps): JSX.Element | null => {
const { getRoundByPollId } = useRound();
const round = useMemo(() => getRoundByPollId(pollId), [pollId, getRoundByPollId]);
const amount = useProjectResults(id, registryAddress as Hex, pollId, (round?.tallyAddress ?? ZeroAddress) as Hex);

if (amount.isLoading) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ProjectDetails = ({ pollId, project, action = undefined }: IProjectDetails
const { bio, websiteUrl, payoutAddress, github, twitter, fundingSources, profileImageUrl, bannerImageUrl } =
metadata.data ?? {};

const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

return (
<div className="relative dark:text-white">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const ProjectItem = ({
action = undefined,
}: IProjectItemProps): JSX.Element => {
const metadata = useProjectMetadata(recipient.metadataUrl);
const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

return (
<article
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { type Hex, zeroAddress } from "viem";

import { InfiniteLoading } from "~/components/InfiniteLoading";
import { useRound } from "~/contexts/Round";
import { useResults, useProjectsResults } from "~/hooks/useResults";
import { useProjects } from "~/hooks/useProjects";
import { useResults } from "~/hooks/useResults";
import { useRoundState } from "~/utils/state";
import { ERoundState } from "~/utils/types";

Expand All @@ -22,9 +23,13 @@ export const ProjectsResults = ({ pollId }: IProjectsResultsProps): JSX.Element
const router = useRouter();
const { getRoundByPollId } = useRound();
const round = useMemo(() => getRoundByPollId(pollId), [pollId, getRoundByPollId]);
const projects = useProjectsResults((round?.registryAddress ?? zeroAddress) as Hex);
const results = useResults(pollId, (round?.registryAddress ?? zeroAddress) as Hex, round?.tallyFile);
const roundState = useRoundState(pollId);
const projects = useProjects((round?.registryAddress ?? zeroAddress) as Hex);
const results = useResults(
pollId,
(round?.registryAddress ?? zeroAddress) as Hex,
(round?.tallyAddress ?? zeroAddress) as Hex,
);
const roundState = useRoundState({ pollId });

const handleAction = useCallback(
(projectId: string) => (e: Event) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface IProjectsProps {
}

export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => {
const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

const { getRoundByPollId } = useRound();
const round = useMemo(() => getRoundByPollId(pollId), [pollId, getRoundByPollId]);
Expand All @@ -35,7 +35,11 @@ export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => {
const { isRegistered } = useMaci();
const { addToBallot, removeFromBallot, ballotContains, getBallot } = useBallot();

const results = useResults(pollId, (round?.registryAddress ?? zeroAddress) as Hex, round?.tallyFile);
const results = useResults(
pollId,
(round?.registryAddress ?? zeroAddress) as Hex,
(round?.tallyAddress ?? zeroAddress) as Hex,
);

const ballot = useMemo(() => getBallot(pollId), [pollId, getBallot]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const RoundTag = ({ state }: IRoundTagProps): JSX.Element => {
};

export const RoundItem = ({ round }: IRoundItemProps): JSX.Element => {
const roundState = useRoundState(round.pollId);
const roundState = useRoundState({ pollId: round.pollId });

return (
<Link href={`/rounds/${round.pollId}`}>
Expand Down
15 changes: 15 additions & 0 deletions packages/interface/src/hooks/useProjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { config } from "~/config";
import { api } from "~/utils/api";

import type { UseTRPCInfiniteQueryResult } from "@trpc/react-query/shared";
import type { IRecipient } from "~/utils/types";

const seed = 0;
export function useProjects(registryAddress: string): UseTRPCInfiniteQueryResult<IRecipient[], unknown, unknown> {
return api.projects.projects.useInfiniteQuery(
{ registryAddress, limit: config.pageSize, seed },
{
getNextPageParam: (_, pages) => pages.length,
},
);
}
38 changes: 20 additions & 18 deletions packages/interface/src/hooks/useResults.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
import { Chain } from "viem";

import { config } from "~/config";
import { api } from "~/utils/api";
import { useRoundState } from "~/utils/state";
import { ERoundState } from "~/utils/types";

import type { UseTRPCInfiniteQueryResult, UseTRPCQueryResult } from "@trpc/react-query/shared";
import type { IRecipient } from "~/utils/types";
import type { UseTRPCQueryResult } from "@trpc/react-query/shared";
import type { IRecipientWithVotes, Tally } from "~/utils/types";

export function useResults(
pollId: string,
registryAddress: string,
tallyFile?: string,
tallyAddress: string,
): UseTRPCQueryResult<{ averageVotes: number; projects: Record<string, { votes: number; voters: number }> }, unknown> {
const roundState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

return api.results.votes.useQuery({ registryAddress, tallyFile }, { enabled: roundState === ERoundState.RESULTS });
return api.results.votes.useQuery({ registryAddress, tallyAddress }, { enabled: roundState === ERoundState.RESULTS });
}

const seed = 0;
export function useProjectsResults(
registryAddress: string,
): UseTRPCInfiniteQueryResult<IRecipient[], unknown, unknown> {
return api.results.projects.useInfiniteQuery(
{ registryAddress, limit: config.pageSize, seed },
{
getNextPageParam: (_, pages) => pages.length,
},
);
tallyAddress: string,
): UseTRPCQueryResult<IRecipientWithVotes[], unknown> {
return api.results.projects.useQuery({ registryAddress, tallyAddress });
}

export function useProjectCount(registryAddress: string, chain: Chain): UseTRPCQueryResult<{ count: number }, unknown> {
Expand All @@ -38,12 +32,20 @@ export function useProjectResults(
id: string,
registryAddress: string,
pollId: string,
tallyFile?: string,
tallyAddress: string,
): UseTRPCQueryResult<{ amount: number }, unknown> {
const appState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

return api.results.project.useQuery(
{ id, registryAddress, tallyFile },
{ enabled: appState === ERoundState.RESULTS },
{ id, registryAddress, tallyAddress },
{ enabled: roundState === ERoundState.RESULTS },
);
}

export function useIsTallied(tallyAddress: string): UseTRPCQueryResult<{ isTallied: boolean }, unknown> {
return api.maci.isTallied.useQuery({ tallyAddress });
}

export function useFetchTallies(): UseTRPCQueryResult<{ tallies: Tally[] }, unknown> {
return api.maci.tallies.useQuery();
}
8 changes: 4 additions & 4 deletions packages/interface/src/layouts/DefaultLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { BaseLayout } from "./BaseLayout";

export const Layout = ({ children = null, ...props }: ILayoutProps): JSX.Element => {
const { address } = useAccount();
const roundState = useRoundState(props.pollId ?? "");
const roundState = useRoundState({ pollId: props.pollId ?? "" });
const { getBallot } = useBallot();
const { isRegistered, gatekeeperTrait } = useMaci();

Expand Down Expand Up @@ -54,8 +54,8 @@ export const Layout = ({ children = null, ...props }: ILayoutProps): JSX.Element

if (roundState === ERoundState.RESULTS) {
links.push({
href: `/rounds/${props.pollId}/stats`,
children: "Stats",
href: `/rounds/${props.pollId}/result`,
children: "Result",
});
}

Expand Down Expand Up @@ -96,7 +96,7 @@ export const LayoutWithSidebar = ({ ...props }: ILayoutProps): JSX.Element => {
const { address } = useAccount();
const { getBallot } = useBallot();

const roundState = useRoundState(props.pollId ?? "");
const roundState = useRoundState({ pollId: props.pollId ?? "" });

const ballot = useMemo(() => getBallot(props.pollId!), [props.pollId, getBallot]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ const ProjectDetailsPage = ({ projectId = "", pollId }: IProjectDetailsProps): J

const projects = useProjectById(projectId, round?.registryAddress ?? zeroAddress);

const appState = useRoundState(pollId);
const roundState = useRoundState({ pollId });

return (
<LayoutWithSidebar eligibilityCheck showBallot showInfo pollId={pollId} sidebar="left">
{appState === ERoundState.APPLICATION && <ReviewBar pollId={pollId} projectId={projectId} />}
{roundState === ERoundState.APPLICATION && <ReviewBar pollId={pollId} projectId={projectId} />}

{projects.data && <ProjectDetails pollId={pollId} project={projects.data as unknown as IRecipient} />}
</LayoutWithSidebar>
Expand Down
Loading

0 comments on commit d3b0a76

Please sign in to comment.