@@ -85,12 +89,14 @@ const Header = ({ navLinks }: IHeaderProps) => {
{navLinks.map((link) => {
- const pageName = `/${link.href.split("/")[1]}`;
+ const isActive =
+ asPath.includes(link.children.toLowerCase()) || (link.children === "Projects" && isRoundIndexPage);
+
return (
-
+
{link.children}
- {appState === EAppState.VOTING && pageName === "/ballot" && ballot.votes.length > 0 && (
+ {roundState === ERoundState.VOTING && link.href.includes("/ballot") && ballot.votes.length > 0 && (
{ballot.votes.length}
diff --git a/packages/interface/src/components/Info.tsx b/packages/interface/src/components/Info.tsx
index fb952103..46ecd5e2 100644
--- a/packages/interface/src/components/Info.tsx
+++ b/packages/interface/src/components/Info.tsx
@@ -2,10 +2,9 @@ import { useRouter } from "next/router";
import { tv } from "tailwind-variants";
import { createComponent } from "~/components/ui";
-import { config } from "~/config";
-import { useMaci } from "~/contexts/Maci";
-import { useAppState } from "~/utils/state";
-import { EInfoCardState, EAppState } from "~/utils/types";
+import { useRound } from "~/contexts/Round";
+import { useRoundState } from "~/utils/state";
+import { EInfoCardState, ERoundState } from "~/utils/types";
import { BallotOverview } from "./BallotOverview";
import { InfoCard } from "./InfoCard";
@@ -25,8 +24,9 @@ const InfoContainer = createComponent(
}),
);
-interface InfoProps {
+interface IInfoProps {
size: string;
+ roundId: string;
showRoundInfo?: boolean;
showAppState?: boolean;
showBallot?: boolean;
@@ -34,73 +34,71 @@ interface InfoProps {
export const Info = ({
size,
+ roundId,
showRoundInfo = false,
showAppState = false,
showBallot = false,
-}: InfoProps): JSX.Element => {
- const { votingEndsAt } = useMaci();
- const appState = useAppState();
+}: IInfoProps): JSX.Element => {
+ const roundState = useRoundState(roundId);
+ const { getRound } = useRound();
+ const round = getRound(roundId);
const { asPath } = useRouter();
const steps = [
{
label: "application",
- state: EAppState.APPLICATION,
- start: config.startsAt,
- end: config.registrationEndsAt,
+ state: ERoundState.APPLICATION,
+ start: round?.startsAt ? new Date(round.startsAt) : new Date(),
+ end: round?.registrationEndsAt ? new Date(round.registrationEndsAt) : new Date(),
},
{
label: "voting",
- state: EAppState.VOTING,
- start: config.registrationEndsAt,
- end: votingEndsAt,
+ state: ERoundState.VOTING,
+ start: round?.registrationEndsAt ? new Date(round.registrationEndsAt) : new Date(),
+ end: round?.votingEndsAt ? new Date(round.votingEndsAt) : new Date(),
},
{
label: "tallying",
- state: EAppState.TALLYING,
- start: votingEndsAt,
- end: config.resultsAt,
+ state: ERoundState.TALLYING,
+ start: round?.votingEndsAt ? new Date(round.votingEndsAt) : new Date(),
+ end: round?.votingEndsAt ? new Date(round.votingEndsAt) : new Date(),
},
{
label: "results",
- state: EAppState.RESULTS,
- start: config.resultsAt,
- end: config.resultsAt,
+ state: ERoundState.RESULTS,
+ start: round?.votingEndsAt ? new Date(round.votingEndsAt) : new Date(),
+ end: round?.votingEndsAt ? new Date(round.votingEndsAt) : new Date(),
},
];
return (
- {showRoundInfo && }
+ {showRoundInfo && }
- {showBallot && }
+ {showBallot && }
- {showRoundInfo && appState === EAppState.VOTING && }
+ {showRoundInfo && roundState === ERoundState.VOTING && }
{showAppState &&
- steps.map(
- (step) =>
- step.start &&
- step.end && (
-
- ),
- )}
+ steps.map((step) => (
+
+ ))}
);
};
-function defineState({ state, appState }: { state: EAppState; appState: EAppState }): EInfoCardState {
- const statesOrder = [EAppState.APPLICATION, EAppState.VOTING, EAppState.TALLYING, EAppState.RESULTS];
+function defineState({ state, roundState }: { state: ERoundState; roundState: ERoundState }): EInfoCardState {
+ const statesOrder = [ERoundState.APPLICATION, ERoundState.VOTING, ERoundState.TALLYING, ERoundState.RESULTS];
const currentStateOrder = statesOrder.indexOf(state);
- const appStateOrder = statesOrder.indexOf(appState);
+ const appStateOrder = statesOrder.indexOf(roundState);
if (currentStateOrder < appStateOrder) {
return EInfoCardState.PASSED;
diff --git a/packages/interface/src/components/InfoCard.tsx b/packages/interface/src/components/InfoCard.tsx
index a5adb23b..871affae 100644
--- a/packages/interface/src/components/InfoCard.tsx
+++ b/packages/interface/src/components/InfoCard.tsx
@@ -1,8 +1,8 @@
-import { format } from "date-fns";
import Image from "next/image";
import { tv } from "tailwind-variants";
import { createComponent } from "~/components/ui";
+import { formatPeriod } from "~/utils/time";
import { EInfoCardState } from "~/utils/types";
const InfoCardContainer = createComponent(
@@ -45,20 +45,6 @@ export const InfoCard = ({ state, title, start, end }: InfoCardProps): JSX.Eleme
)}
-
{formatDateString({ start, end })}
+
{formatPeriod({ start, end })}
);
-
-function formatDateString({ start, end }: { start: Date; end: Date }): string {
- const fullFormat = "d MMM yyyy";
-
- if (start.getMonth() === end.getMonth() && start.getFullYear() === end.getFullYear()) {
- return `${start.getDate()} - ${format(end, fullFormat)}`;
- }
-
- if (start.getFullYear() === end.getFullYear()) {
- return `${format(start, "d MMM")} - ${format(end, fullFormat)}`;
- }
-
- return `${format(start, fullFormat)} - ${format(end, fullFormat)}`;
-}
diff --git a/packages/interface/src/components/JoinButton.tsx b/packages/interface/src/components/JoinButton.tsx
index cddb616c..b488a347 100644
--- a/packages/interface/src/components/JoinButton.tsx
+++ b/packages/interface/src/components/JoinButton.tsx
@@ -2,35 +2,24 @@ import { useCallback } from "react";
import { toast } from "sonner";
import { useMaci } from "~/contexts/Maci";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
import { Button } from "./ui/Button";
export const JoinButton = (): JSX.Element => {
const { isLoading, isRegistered, isEligibleToVote, onSignup } = useMaci();
- const appState = useAppState();
const onError = useCallback(() => toast.error("Signup error"), []);
const handleSignup = useCallback(() => onSignup(onError), [onSignup, onError]);
return (
- {appState === EAppState.VOTING && !isEligibleToVote && (
- You are not allowed to vote
- )}
+ {!isEligibleToVote && You are not allowed to vote }
- {appState === EAppState.VOTING && isEligibleToVote && !isRegistered && (
+ {isEligibleToVote && !isRegistered && (
Voter sign up
)}
-
- {appState === EAppState.TALLYING && (
- Voting round is over, the result is tallying.
- )}
-
- {appState === EAppState.RESULTS && View results }
);
};
diff --git a/packages/interface/src/components/RoundInfo.tsx b/packages/interface/src/components/RoundInfo.tsx
index 75c9f06f..6281c17b 100644
--- a/packages/interface/src/components/RoundInfo.tsx
+++ b/packages/interface/src/components/RoundInfo.tsx
@@ -3,7 +3,11 @@ import Image from "next/image";
import { Heading } from "~/components/ui/Heading";
import { config } from "~/config";
-export const RoundInfo = (): JSX.Element => (
+interface IRoundInfoProps {
+ roundId: string;
+}
+
+export const RoundInfo = ({ roundId }: IRoundInfoProps): JSX.Element => (
Round
@@ -11,7 +15,7 @@ export const RoundInfo = (): JSX.Element => (
{config.roundLogo && }
- {config.roundId}
+ {roundId}
diff --git a/packages/interface/src/components/ui/Navigation.tsx b/packages/interface/src/components/ui/Navigation.tsx
index 6f50c170..e2999649 100644
--- a/packages/interface/src/components/ui/Navigation.tsx
+++ b/packages/interface/src/components/ui/Navigation.tsx
@@ -2,12 +2,13 @@ import Link from "next/link";
interface INavigationProps {
projectName: string;
+ roundId: string;
}
-export const Navigation = ({ projectName }: INavigationProps): JSX.Element => (
+export const Navigation = ({ projectName, roundId }: INavigationProps): JSX.Element => (
- Projects
+ Projects
{">"}
diff --git a/packages/interface/src/config.ts b/packages/interface/src/config.ts
index 28fa3f6e..44e80d73 100644
--- a/packages/interface/src/config.ts
+++ b/packages/interface/src/config.ts
@@ -7,8 +7,6 @@ export const metadata = {
image: "/api/og",
};
-const parseDate = (env?: string) => (env ? new Date(env) : undefined);
-
// URLs for the EAS GraphQL endpoint for each chain
const easScanUrl = {
ethereum: "https://easscan.org/graphql",
@@ -76,13 +74,9 @@ export const config = {
pageSize: 3 * 4,
// TODO: temp solution until we come up with solid one
// https://github.com/privacy-scaling-explorations/maci-platform/issues/31
- voteLimit: 50,
- startsAt: parseDate(process.env.NEXT_PUBLIC_START_DATE),
- registrationEndsAt: parseDate(process.env.NEXT_PUBLIC_REGISTRATION_END_DATE),
- resultsAt: parseDate(process.env.NEXT_PUBLIC_RESULTS_DATE),
tokenName: process.env.NEXT_PUBLIC_TOKEN_NAME!,
- eventName: process.env.NEXT_PUBLIC_EVENT_NAME ?? "MACI-PLATFORM",
- roundId: process.env.NEXT_PUBLIC_ROUND_ID!,
+ eventName: process.env.NEXT_PUBLIC_EVENT_NAME ?? "Add your event name",
+ eventDescription: process.env.NEXT_PUBLIC_EVENT_DESCRIPTION ?? "Add your event description",
admin: (process.env.NEXT_PUBLIC_ADMIN_ADDRESS ?? "") as `0x${string}`,
network: wagmiChains[process.env.NEXT_PUBLIC_CHAIN_NAME as keyof typeof wagmiChains],
maciAddress: process.env.NEXT_PUBLIC_MACI_ADDRESS,
@@ -100,7 +94,6 @@ export const theme = {
export const eas = {
url: easScanUrl[process.env.NEXT_PUBLIC_CHAIN_NAME as keyof typeof easScanUrl],
- attesterAddress: process.env.NEXT_PUBLIC_APPROVED_APPLICATIONS_ATTESTER ?? "",
contracts: {
eas: easContractAddresses[process.env.NEXT_PUBLIC_CHAIN_NAME as keyof typeof easContractAddresses],
diff --git a/packages/interface/src/contexts/Round.tsx b/packages/interface/src/contexts/Round.tsx
new file mode 100644
index 00000000..a5415337
--- /dev/null
+++ b/packages/interface/src/contexts/Round.tsx
@@ -0,0 +1,44 @@
+import React, { createContext, useContext, useMemo, useCallback } from "react";
+
+import type { RoundContextType, RoundProviderProps } from "./types";
+import type { Round } from "~/features/rounds/types";
+
+export const RoundContext = createContext
(undefined);
+
+export const RoundProvider: React.FC = ({ children }: RoundProviderProps) => {
+ const rounds = [
+ {
+ roundId: "open-rpgf-1",
+ description: "This is the description of this round, please add your own description.",
+ startsAt: 1723477832000,
+ registrationEndsAt: 1723487832000,
+ votingEndsAt: 1724009826000,
+ tallyURL: "https://upblxu2duoxmkobt.public.blob.vercel-storage.com/tally.json",
+ },
+ ];
+
+ const getRound = useCallback(
+ (roundId: string): Round | undefined => rounds.find((round) => round.roundId === roundId),
+ [rounds],
+ );
+
+ const value = useMemo(
+ () => ({
+ rounds,
+ getRound,
+ }),
+ [rounds, getRound],
+ );
+
+ return {children} ;
+};
+
+export const useRound = (): RoundContextType => {
+ const roundContext = useContext(RoundContext);
+
+ if (!roundContext) {
+ throw new Error("Should use context inside provider.");
+ }
+
+ return roundContext;
+};
diff --git a/packages/interface/src/contexts/types.ts b/packages/interface/src/contexts/types.ts
index 29eab6bd..2637d0b8 100644
--- a/packages/interface/src/contexts/types.ts
+++ b/packages/interface/src/contexts/types.ts
@@ -2,6 +2,7 @@ import { type TallyData, type IGetPollData, type GatekeeperTrait } from "maci-cl
import { type ReactNode } from "react";
import type { Ballot, Vote } from "~/features/ballot/types";
+import type { Round } from "~/features/rounds/types";
export interface IVoteArgs {
voteOptionIndex: bigint;
@@ -47,3 +48,12 @@ export interface BallotContextType {
export interface BallotProviderProps {
children: ReactNode;
}
+
+export interface RoundContextType {
+ rounds: Round[];
+ getRound: (roundId: string) => Round | undefined;
+}
+
+export interface RoundProviderProps {
+ children: ReactNode;
+}
diff --git a/packages/interface/src/env.js b/packages/interface/src/env.js
index 11cb918a..5ff03102 100644
--- a/packages/interface/src/env.js
+++ b/packages/interface/src/env.js
@@ -35,27 +35,12 @@ module.exports = createEnv({
NEXT_PUBLIC_FEEDBACK_URL: z.string().default("#"),
// EAS Schemas
- NEXT_PUBLIC_APPROVED_APPLICATIONS_SCHEMA: z
- .string()
- .default("0xebbf697d5d3ca4b53579917ffc3597fb8d1a85b8c6ca10ec10039709903b9277"),
- NEXT_PUBLIC_APPROVED_APPLICATIONS_ATTESTER: z.string().default("0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9"),
- NEXT_PUBLIC_APPLICATIONS_SCHEMA: z
- .string()
- .default("0x76e98cce95f3ba992c2ee25cef25f756495147608a3da3aa2e5ca43109fe77cc"),
- NEXT_PUBLIC_BADGEHOLDER_SCHEMA: z
- .string()
- .default("0xfdcfdad2dbe7489e0ce56b260348b7f14e8365a8a325aef9834818c00d46b31b"),
- NEXT_PUBLIC_BADGEHOLDER_ATTESTER: z.string().default("0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9"),
- NEXT_PUBLIC_PROFILE_SCHEMA: z
- .string()
- .default("0xac4c92fc5c7babed88f78a917cdbcdc1c496a8f4ab2d5b2ec29402736b2cf929"),
-
NEXT_PUBLIC_ADMIN_ADDRESS: z.string().startsWith("0x"),
NEXT_PUBLIC_APPROVAL_SCHEMA: z.string().startsWith("0x"),
NEXT_PUBLIC_METADATA_SCHEMA: z.string().startsWith("0x"),
- NEXT_PUBLIC_EVENT_NAME: z.string().optional(),
- NEXT_PUBLIC_ROUND_ID: z.string(),
+ NEXT_PUBLIC_EVENT_NAME: z.string().default("Add your event name"),
+ NEXT_PUBLIC_EVENT_DESCRIPTION: z.string().default("Add your event description"),
NEXT_PUBLIC_WALLETCONNECT_ID: z.string().optional(),
NEXT_PUBLIC_ALCHEMY_ID: z.string().optional(),
@@ -81,13 +66,6 @@ module.exports = createEnv({
NEXT_PUBLIC_FEEDBACK_URL: process.env.NEXT_PUBLIC_FEEDBACK_URL,
- NEXT_PUBLIC_APPROVED_APPLICATIONS_SCHEMA: process.env.NEXT_PUBLIC_APPROVED_APPLICATIONS_SCHEMA,
- NEXT_PUBLIC_APPROVED_APPLICATIONS_ATTESTER: process.env.NEXT_PUBLIC_APPROVED_APPLICATIONS_ATTESTER,
- NEXT_PUBLIC_APPLICATIONS_SCHEMA: process.env.NEXT_PUBLIC_APPLICATIONS_SCHEMA,
- NEXT_PUBLIC_BADGEHOLDER_SCHEMA: process.env.NEXT_PUBLIC_BADGEHOLDER_SCHEMA,
- NEXT_PUBLIC_BADGEHOLDER_ATTESTER: process.env.NEXT_PUBLIC_BADGEHOLDER_ATTESTER,
- NEXT_PUBLIC_PROFILE_SCHEMA: process.env.NEXT_PUBLIC_PROFILE_SCHEMA,
-
NEXT_PUBLIC_WALLETCONNECT_ID: process.env.NEXT_PUBLIC_WALLETCONNECT_ID,
NEXT_PUBLIC_ALCHEMY_ID: process.env.NEXT_PUBLIC_ALCHEMY_ID,
@@ -96,7 +74,7 @@ module.exports = createEnv({
NEXT_PUBLIC_METADATA_SCHEMA: process.env.NEXT_PUBLIC_METADATA_SCHEMA,
NEXT_PUBLIC_EVENT_NAME: process.env.NEXT_PUBLIC_EVENT_NAME,
- NEXT_PUBLIC_ROUND_ID: process.env.NEXT_PUBLIC_ROUND_ID,
+ NEXT_PUBLIC_EVENT_DESCRIPTION: process.env.NEXT_PUBLIC_EVENT_DESCRIPTION,
NEXT_PUBLIC_MACI_ADDRESS: process.env.NEXT_PUBLIC_MACI_ADDRESS,
NEXT_PUBLIC_MACI_START_BLOCK: process.env.NEXT_PUBLIC_MACI_START_BLOCK,
diff --git a/packages/interface/src/features/applications/components/ApplicationForm.tsx b/packages/interface/src/features/applications/components/ApplicationForm.tsx
index 70b6bcff..a17e20e7 100644
--- a/packages/interface/src/features/applications/components/ApplicationForm.tsx
+++ b/packages/interface/src/features/applications/components/ApplicationForm.tsx
@@ -18,7 +18,11 @@ import { ApplicationSteps } from "./ApplicationSteps";
import { ImpactTags } from "./ImpactTags";
import { ReviewApplicationDetails } from "./ReviewApplicationDetails";
-export const ApplicationForm = (): JSX.Element => {
+interface IApplicationFormProps {
+ roundId: string;
+}
+
+export const ApplicationForm = ({ roundId }: IApplicationFormProps): JSX.Element => {
const clearDraft = useLocalStorage("application-draft")[2];
const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork();
@@ -60,6 +64,7 @@ export const ApplicationForm = (): JSX.Element => {
toast.error("Application create error", {
description: err.reason ?? err.data?.message,
}),
+ roundId,
});
const { error: createError } = create;
diff --git a/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx b/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx
index 17d8bfab..7c546aa9 100644
--- a/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx
+++ b/packages/interface/src/features/applications/components/ApplicationsToApprove.tsx
@@ -20,10 +20,14 @@ import { ApplicationHeader } from "./ApplicationHeader";
import { ApplicationItem } from "./ApplicationItem";
import { ApproveButton } from "./ApproveButton";
-export const ApplicationsToApprove = (): JSX.Element => {
- const applications = useApplications();
- const approved = useApprovedApplications();
- const approve = useApproveApplication();
+interface IApplicationsToApproveProps {
+ roundId: string;
+}
+
+export const ApplicationsToApprove = ({ roundId }: IApplicationsToApproveProps): JSX.Element => {
+ const applications = useApplications(roundId);
+ const approved = useApprovedApplications(roundId);
+ const approve = useApproveApplication({ roundId });
const [refetchedData, setRefetchedData] = useState();
const approvedById = useMemo(
@@ -39,7 +43,7 @@ export const ApplicationsToApprove = (): JSX.Element => {
useEffect(() => {
const fetchData = async () => {
- const ret = await fetchApprovedApplications();
+ const ret = await fetchApprovedApplications(roundId);
setRefetchedData(ret);
};
diff --git a/packages/interface/src/features/applications/components/ReviewBar.tsx b/packages/interface/src/features/applications/components/ReviewBar.tsx
index 9f3b1e6a..70cafc58 100644
--- a/packages/interface/src/features/applications/components/ReviewBar.tsx
+++ b/packages/interface/src/features/applications/components/ReviewBar.tsx
@@ -14,14 +14,15 @@ import { useApproveApplication } from "../hooks/useApproveApplication";
import { useApprovedApplications } from "../hooks/useApprovedApplications";
interface IReviewBarProps {
+ roundId: string;
projectId: string;
}
-export const ReviewBar = ({ projectId }: IReviewBarProps): JSX.Element => {
+export const ReviewBar = ({ roundId, projectId }: IReviewBarProps): JSX.Element => {
const isAdmin = useIsAdmin();
const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork();
- const rawReturn = useApprovedApplications([projectId]);
+ const rawReturn = useApprovedApplications(roundId, [projectId]);
const [refetchedData, setRefetchedData] = useState();
const approved = useMemo(
@@ -29,7 +30,7 @@ export const ReviewBar = ({ projectId }: IReviewBarProps): JSX.Element => {
[rawReturn.data, refetchedData],
);
- const approve = useApproveApplication();
+ const approve = useApproveApplication({ roundId });
const onClick = useCallback(() => {
approve.mutate([projectId]);
@@ -37,7 +38,7 @@ export const ReviewBar = ({ projectId }: IReviewBarProps): JSX.Element => {
useEffect(() => {
const fetchData = async () => {
- const ret = await fetchApprovedApplications([projectId]);
+ const ret = await fetchApprovedApplications(roundId, [projectId]);
setRefetchedData(ret);
};
diff --git a/packages/interface/src/features/applications/hooks/useApplications.ts b/packages/interface/src/features/applications/hooks/useApplications.ts
index bd92b59d..3ba0a3e0 100644
--- a/packages/interface/src/features/applications/hooks/useApplications.ts
+++ b/packages/interface/src/features/applications/hooks/useApplications.ts
@@ -3,6 +3,6 @@ import { api } from "~/utils/api";
import type { UseTRPCQueryResult } from "@trpc/react-query/shared";
import type { Attestation } from "~/utils/types";
-export function useApplications(): UseTRPCQueryResult {
- return api.applications.list.useQuery({});
+export function useApplications(roundId: string): UseTRPCQueryResult {
+ return api.applications.list.useQuery({ roundId });
}
diff --git a/packages/interface/src/features/applications/hooks/useApproveApplication.ts b/packages/interface/src/features/applications/hooks/useApproveApplication.ts
index e1d052f7..fd8dd6b4 100644
--- a/packages/interface/src/features/applications/hooks/useApproveApplication.ts
+++ b/packages/interface/src/features/applications/hooks/useApproveApplication.ts
@@ -2,13 +2,14 @@ import { type Transaction } from "@ethereum-attestation-service/eas-sdk";
import { type UseMutationResult, useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
-import { config, eas } from "~/config";
+import { eas } from "~/config";
import { type TransactionError } from "~/features/voters/hooks/useApproveVoters";
import { useAttest } from "~/hooks/useEAS";
import { useEthersSigner } from "~/hooks/useEthersSigner";
import { createAttestation } from "~/lib/eas/createAttestation";
-export function useApproveApplication(opts?: {
+export function useApproveApplication(opts: {
+ roundId: string;
onSuccess?: () => void;
}): UseMutationResult, Error | TransactionError, string[]> {
const attest = useAttest();
@@ -24,7 +25,7 @@ export function useApproveApplication(opts?: {
applicationIds.map((refUID) =>
createAttestation(
{
- values: { type: "application", round: config.roundId },
+ values: { type: "application", round: opts.roundId },
schemaUID: eas.schemas.approval,
refUID,
},
@@ -36,7 +37,7 @@ export function useApproveApplication(opts?: {
},
onSuccess: () => {
toast.success("Application approved successfully!");
- opts?.onSuccess?.();
+ opts.onSuccess?.();
},
onError: (err: { reason?: string; data?: { message: string } }) =>
toast.error("Application approve error", {
diff --git a/packages/interface/src/features/applications/hooks/useApprovedApplications.ts b/packages/interface/src/features/applications/hooks/useApprovedApplications.ts
index 6c3ed93b..9e0e1974 100644
--- a/packages/interface/src/features/applications/hooks/useApprovedApplications.ts
+++ b/packages/interface/src/features/applications/hooks/useApprovedApplications.ts
@@ -3,6 +3,6 @@ import { api } from "~/utils/api";
import type { UseTRPCQueryResult } from "@trpc/react-query/shared";
import type { Attestation } from "~/utils/types";
-export function useApprovedApplications(ids?: string[]): UseTRPCQueryResult {
- return api.applications.approvals.useQuery({ ids });
+export function useApprovedApplications(roundId: string, ids?: string[]): UseTRPCQueryResult {
+ return api.applications.approvals.useQuery({ roundId, ids });
}
diff --git a/packages/interface/src/features/applications/hooks/useCreateApplication.ts b/packages/interface/src/features/applications/hooks/useCreateApplication.ts
index b7cefa9c..e701039c 100644
--- a/packages/interface/src/features/applications/hooks/useCreateApplication.ts
+++ b/packages/interface/src/features/applications/hooks/useCreateApplication.ts
@@ -1,6 +1,6 @@
import { type UseMutationResult, useMutation } from "@tanstack/react-query";
-import { config, eas } from "~/config";
+import { eas } from "~/config";
import { type TransactionError } from "~/features/voters/hooks/useApproveVoters";
import { useAttest, useCreateAttestation } from "~/hooks/useEAS";
import { useUploadMetadata } from "~/hooks/useMetadata";
@@ -20,6 +20,7 @@ export type TUseCreateApplicationReturn = Omit<
export function useCreateApplication(options: {
onSuccess: (data: Transaction) => void;
onError: (err: TransactionError) => void;
+ roundId: string;
}): TUseCreateApplicationReturn {
const attestation = useCreateAttestation();
const attest = useAttest();
@@ -52,7 +53,7 @@ export function useCreateApplication(options: {
metadataType: 0, // "http"
metadataPtr,
type: "application",
- round: config.roundId,
+ round: options.roundId,
},
}),
),
diff --git a/packages/interface/src/features/ballot/components/BallotConfirmation.tsx b/packages/interface/src/features/ballot/components/BallotConfirmation.tsx
index 6620d8e6..5beb6b29 100644
--- a/packages/interface/src/features/ballot/components/BallotConfirmation.tsx
+++ b/packages/interface/src/features/ballot/components/BallotConfirmation.tsx
@@ -9,10 +9,11 @@ import { Heading } from "~/components/ui/Heading";
import { Notice } from "~/components/ui/Notice";
import { config } from "~/config";
import { useBallot } from "~/contexts/Ballot";
+import { useRound } from "~/contexts/Round";
import { useProjectCount } from "~/features/projects/hooks/useProjects";
import { formatNumber } from "~/utils/formatNumber";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
import { ProjectAvatarWithName } from "./ProjectAvatarWithName";
@@ -25,11 +26,17 @@ const Card = createComponent(
}),
);
-export const BallotConfirmation = (): JSX.Element => {
+interface IBallotConfirmationProps {
+ roundId: string;
+}
+
+export const BallotConfirmation = ({ roundId }: IBallotConfirmationProps): JSX.Element => {
const { ballot, sumBallot } = useBallot();
const allocations = ballot.votes;
- const { data: projectCount } = useProjectCount();
- const appState = useAppState();
+ const { data: projectCount } = useProjectCount(roundId);
+ const roundState = useRoundState(roundId);
+ const { getRound } = useRound();
+ const round = getRound(roundId);
const sum = useMemo(() => formatNumber(sumBallot(ballot.votes)), [ballot, sumBallot]);
@@ -40,14 +47,14 @@ export const BallotConfirmation = (): JSX.Element => {
- {`Thank you for participating in ${config.eventName} ${config.roundId} round.`}
+ {`Thank you for participating in ${config.eventName} ${roundId} round.`}
Summary of your ballot
- {`Round you voted in: ${config.roundId}`}
+ {`Round you voted in: ${roundId}`}
@@ -70,12 +77,14 @@ export const BallotConfirmation = (): JSX.Element => {
- {appState === EAppState.VOTING && (
+ {roundState === ERoundState.VOTING && (
Changed your mind?
diff --git a/packages/interface/src/features/ballot/components/SubmitBallotButton.tsx b/packages/interface/src/features/ballot/components/SubmitBallotButton.tsx
index 3961bc2d..cc69f24e 100644
--- a/packages/interface/src/features/ballot/components/SubmitBallotButton.tsx
+++ b/packages/interface/src/features/ballot/components/SubmitBallotButton.tsx
@@ -8,12 +8,16 @@ import { useBallot } from "~/contexts/Ballot";
import { useMaci } from "~/contexts/Maci";
import { useProjectIdMapping } from "~/features/projects/hooks/useProjects";
-export const SubmitBallotButton = (): JSX.Element => {
+interface ISubmitBallotButtonProps {
+ roundId: string;
+}
+
+export const SubmitBallotButton = ({ roundId }: ISubmitBallotButtonProps): JSX.Element => {
const router = useRouter();
const [isOpen, setOpen] = useState(false);
const { onVote, isLoading, initialVoiceCredits } = useMaci();
const { ballot, publishBallot, sumBallot } = useBallot();
- const projectIndices = useProjectIdMapping(ballot);
+ const projectIndices = useProjectIdMapping(ballot, roundId);
const ableToSubmit = useMemo(
() => sumBallot(ballot.votes) <= initialVoiceCredits,
diff --git a/packages/interface/src/features/filter/types/index.ts b/packages/interface/src/features/filter/types/index.ts
index dc0f3e30..b6cf2fab 100644
--- a/packages/interface/src/features/filter/types/index.ts
+++ b/packages/interface/src/features/filter/types/index.ts
@@ -17,6 +17,7 @@ export const FilterSchema = z.object({
sortOrder: z.nativeEnum(SortOrder).default(SortOrder.asc),
search: z.preprocess((v) => (v === "null" || v === "undefined" ? null : v), z.string().nullish()),
needApproval: z.boolean().optional().default(true),
+ roundId: z.string(),
});
export type Filter = z.infer
;
diff --git a/packages/interface/src/features/signup/components/FaqItem.tsx b/packages/interface/src/features/home/components/FaqItem.tsx
similarity index 100%
rename from packages/interface/src/features/signup/components/FaqItem.tsx
rename to packages/interface/src/features/home/components/FaqItem.tsx
diff --git a/packages/interface/src/features/signup/components/FaqList.tsx b/packages/interface/src/features/home/components/FaqList.tsx
similarity index 100%
rename from packages/interface/src/features/signup/components/FaqList.tsx
rename to packages/interface/src/features/home/components/FaqList.tsx
diff --git a/packages/interface/src/features/signup/types.ts b/packages/interface/src/features/home/types.ts
similarity index 100%
rename from packages/interface/src/features/signup/types.ts
rename to packages/interface/src/features/home/types.ts
diff --git a/packages/interface/src/features/projects/components/ProjectAwarded.tsx b/packages/interface/src/features/projects/components/ProjectAwarded.tsx
index e5351bd8..3da3170c 100644
--- a/packages/interface/src/features/projects/components/ProjectAwarded.tsx
+++ b/packages/interface/src/features/projects/components/ProjectAwarded.tsx
@@ -5,12 +5,13 @@ import { useProjectResults } from "~/hooks/useResults";
import { formatNumber } from "~/utils/formatNumber";
export interface IProjectAwardedProps {
+ roundId: string;
id?: string;
}
-export const ProjectAwarded = ({ id = "" }: IProjectAwardedProps): JSX.Element | null => {
+export const ProjectAwarded = ({ roundId, id = "" }: IProjectAwardedProps): JSX.Element | null => {
const { pollData } = useMaci();
- const amount = useProjectResults(id, pollData);
+ const amount = useProjectResults(id, roundId, pollData);
if (amount.isLoading) {
return null;
diff --git a/packages/interface/src/features/projects/components/ProjectDetails.tsx b/packages/interface/src/features/projects/components/ProjectDetails.tsx
index f640620e..1e93c2bc 100644
--- a/packages/interface/src/features/projects/components/ProjectDetails.tsx
+++ b/packages/interface/src/features/projects/components/ProjectDetails.tsx
@@ -5,8 +5,8 @@ import { Navigation } from "~/components/ui/Navigation";
import { ProjectAvatar } from "~/features/projects/components/ProjectAvatar";
import { ProjectBanner } from "~/features/projects/components/ProjectBanner";
import { VotingWidget } from "~/features/projects/components/VotingWidget";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
import type { Attestation } from "~/utils/types";
@@ -16,12 +16,14 @@ import { ProjectContacts } from "./ProjectContacts";
import { ProjectDescriptionSection } from "./ProjectDescriptionSection";
export interface IProjectDetailsProps {
+ roundId: string;
action?: ReactNode;
projectId?: string;
attestation?: Attestation;
}
const ProjectDetails = ({
+ roundId,
projectId = "",
attestation = undefined,
action = undefined,
@@ -31,12 +33,12 @@ const ProjectDetails = ({
const { bio, websiteUrl, payoutAddress, github, twitter, fundingSources, profileImageUrl, bannerImageUrl } =
metadata.data ?? {};
- const appState = useAppState();
+ const roundState = useRoundState(roundId);
return (
-
+
@@ -52,7 +54,7 @@ const ProjectDetails = ({
{attestation?.name}
- {appState === EAppState.VOTING && }
+ {roundState === ERoundState.VOTING && }
diff --git a/packages/interface/src/features/projects/components/ProjectItem.tsx b/packages/interface/src/features/projects/components/ProjectItem.tsx
index 9af1dd0f..6bd8a483 100644
--- a/packages/interface/src/features/projects/components/ProjectItem.tsx
+++ b/packages/interface/src/features/projects/components/ProjectItem.tsx
@@ -5,8 +5,8 @@ import { Heading } from "~/components/ui/Heading";
import { Skeleton } from "~/components/ui/Skeleton";
import { config } from "~/config";
import { formatNumber } from "~/utils/formatNumber";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
import type { Attestation } from "~/utils/types";
@@ -18,6 +18,7 @@ import { ProjectAvatar } from "./ProjectAvatar";
import { ProjectBanner } from "./ProjectBanner";
export interface IProjectItemProps {
+ roundId: string;
attestation: Attestation;
isLoading: boolean;
state?: EProjectState;
@@ -25,13 +26,14 @@ export interface IProjectItemProps {
}
export const ProjectItem = ({
+ roundId,
attestation,
isLoading,
state = undefined,
action = undefined,
}: IProjectItemProps): JSX.Element => {
const metadata = useProjectMetadata(attestation.metadataPtr);
- const appState = useAppState();
+ const roundState = useRoundState(roundId);
return (
- {!isLoading && state !== undefined && action && appState === EAppState.VOTING && (
+ {!isLoading && state !== undefined && action && roundState === ERoundState.VOTING && (
{state === EProjectState.DEFAULT && (
@@ -71,7 +73,7 @@ export const ProjectItem = ({
{state === EProjectState.ADDED && (
Added
-
+
)}
diff --git a/packages/interface/src/features/projects/components/ProjectsResults.tsx b/packages/interface/src/features/projects/components/ProjectsResults.tsx
index 6e6ba5bc..cc12cf3e 100644
--- a/packages/interface/src/features/projects/components/ProjectsResults.tsx
+++ b/packages/interface/src/features/projects/components/ProjectsResults.tsx
@@ -6,19 +6,23 @@ import { useCallback } from "react";
import { InfiniteLoading } from "~/components/InfiniteLoading";
import { useMaci } from "~/contexts/Maci";
import { useResults, useProjectsResults } from "~/hooks/useResults";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
import { EProjectState } from "../types";
import { ProjectItem, ProjectItemAwarded } from "./ProjectItem";
-export const ProjectsResults = (): JSX.Element => {
+interface IProjectsResultsProps {
+ roundId: string;
+}
+
+export const ProjectsResults = ({ roundId }: IProjectsResultsProps): JSX.Element => {
const router = useRouter();
const { pollData } = useMaci();
- const projects = useProjectsResults(pollData);
- const results = useResults();
- const appState = useAppState();
+ const projects = useProjectsResults(roundId, pollData);
+ const results = useResults(roundId);
+ const roundState = useRoundState(roundId);
const handleAction = useCallback(
(projectId: string) => (e: Event) => {
@@ -33,7 +37,7 @@ export const ProjectsResults = (): JSX.Element => {
{...projects}
renderItem={(item, { isLoading }) => (
- {!results.isLoading && appState === EAppState.RESULTS ? (
+ {!results.isLoading && roundState === ERoundState.RESULTS ? (
) : null}
@@ -41,6 +45,7 @@ export const ProjectsResults = (): JSX.Element => {
action={handleAction(item.id)}
attestation={item}
isLoading={isLoading}
+ roundId={roundId}
state={EProjectState.SUBMITTED}
/>
diff --git a/packages/interface/src/features/projects/hooks/useProjects.ts b/packages/interface/src/features/projects/hooks/useProjects.ts
index 8dc1fdee..c338fada 100644
--- a/packages/interface/src/features/projects/hooks/useProjects.ts
+++ b/packages/interface/src/features/projects/hooks/useProjects.ts
@@ -11,6 +11,7 @@ import type { Ballot } from "~/features/ballot/types";
import type { Attestation } from "~/utils/types";
interface IUseSearchProjectsProps {
+ roundId: string;
filterOverride?: Partial;
needApproval?: boolean;
}
@@ -25,21 +26,22 @@ export function useProjectsById(ids: string[]): UseTRPCQueryResult {
const { ...filter } = useFilter();
return api.projects.search.useInfiniteQuery(
- { seed, ...filter, ...filterOverride, needApproval },
+ { roundId, seed, ...filter, ...filterOverride, needApproval },
{
getNextPageParam: (_, pages) => pages.length,
},
);
}
-export function useProjectIdMapping(ballot: Ballot): Record {
- const { data } = api.projects.allApproved.useQuery();
+export function useProjectIdMapping(ballot: Ballot, roundId: string): Record {
+ const { data } = api.projects.allApproved.useQuery({ roundId });
const projectIndices = useMemo(
() =>
@@ -59,6 +61,6 @@ export function useProjectMetadata(metadataPtr?: string): UseTRPCQueryResult(metadataPtr);
}
-export function useProjectCount(): UseTRPCQueryResult<{ count: number }, unknown> {
- return api.projects.count.useQuery();
+export function useProjectCount(roundId: string): UseTRPCQueryResult<{ count: number }, unknown> {
+ return api.projects.count.useQuery({ roundId });
}
diff --git a/packages/interface/src/features/projects/components/Projects.tsx b/packages/interface/src/features/rounds/components/Projects.tsx
similarity index 75%
rename from packages/interface/src/features/projects/components/Projects.tsx
rename to packages/interface/src/features/rounds/components/Projects.tsx
index d8af65d8..ebbd5fa6 100644
--- a/packages/interface/src/features/projects/components/Projects.tsx
+++ b/packages/interface/src/features/rounds/components/Projects.tsx
@@ -10,21 +10,24 @@ import { Heading } from "~/components/ui/Heading";
import { useBallot } from "~/contexts/Ballot";
import { useMaci } from "~/contexts/Maci";
import { useResults } from "~/hooks/useResults";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
-import { useSearchProjects } from "../hooks/useProjects";
-import { EProjectState } from "../types";
+import { ProjectItem, ProjectItemAwarded } from "../../projects/components/ProjectItem";
+import { useSearchProjects } from "../../projects/hooks/useProjects";
+import { EProjectState } from "../../projects/types";
-import { ProjectItem, ProjectItemAwarded } from "./ProjectItem";
+export interface IProjectsProps {
+ roundId?: string;
+}
-export const Projects = (): JSX.Element => {
- const appState = useAppState();
- const projects = useSearchProjects({ needApproval: appState !== EAppState.APPLICATION });
+export const Projects = ({ roundId = "" }: IProjectsProps): JSX.Element => {
+ const appState = useRoundState(roundId);
+ const projects = useSearchProjects({ roundId, needApproval: appState !== ERoundState.APPLICATION });
const { pollData, pollId, isRegistered } = useMaci();
const { addToBallot, removeFromBallot, ballotContains, ballot } = useBallot();
- const results = useResults(pollData);
+ const results = useResults(roundId, pollData);
const handleAction = useCallback(
(projectId: string) => (e: Event) => {
@@ -66,7 +69,7 @@ export const Projects = (): JSX.Element => {
return (
- {appState === EAppState.APPLICATION && (
+ {appState === ERoundState.APPLICATION && (
@@ -78,7 +81,7 @@ export const Projects = (): JSX.Element => {
/>
)}
- {appState === EAppState.TALLYING && (
+ {(appState === ERoundState.TALLYING || appState === ERoundState.RESULTS) && (
@@ -106,9 +109,9 @@ export const Projects = (): JSX.Element => {
- {!results.isLoading && appState === EAppState.RESULTS ? (
+ {!results.isLoading && appState === ERoundState.RESULTS ? (
) : null}
@@ -116,6 +119,7 @@ export const Projects = (): JSX.Element => {
action={handleAction(item.id)}
attestation={item}
isLoading={isLoading}
+ roundId={roundId}
state={defineState(item.id)}
/>
diff --git a/packages/interface/src/features/rounds/components/RoundItem.tsx b/packages/interface/src/features/rounds/components/RoundItem.tsx
new file mode 100644
index 00000000..2033dc11
--- /dev/null
+++ b/packages/interface/src/features/rounds/components/RoundItem.tsx
@@ -0,0 +1,65 @@
+import clsx from "clsx";
+import Link from "next/link";
+import { useMemo } from "react";
+import { FiCalendar } from "react-icons/fi";
+
+import { Heading } from "~/components/ui/Heading";
+import { useRoundState } from "~/utils/state";
+import { formatPeriod } from "~/utils/time";
+import { ERoundState } from "~/utils/types";
+
+import type { Round } from "~/features/rounds/types";
+
+interface ITimeBarProps {
+ start: Date;
+ end: Date;
+}
+
+interface IRoundTagProps {
+ isOpen: boolean;
+}
+
+interface IRoundItemProps {
+ round: Round;
+}
+
+const TimeBar = ({ start, end }: ITimeBarProps): JSX.Element => {
+ const periodString = useMemo(() => formatPeriod({ start, end }), [start, end]);
+
+ return (
+
+ );
+};
+
+const RoundTag = ({ isOpen }: IRoundTagProps): JSX.Element => (
+
+ {isOpen ? "Voting Open" : "Round Closed"}
+
+);
+
+export const RoundItem = ({ round }: IRoundItemProps): JSX.Element => {
+ const roundState = useRoundState(round.roundId);
+
+ return (
+
+
+
+
+
{round.roundId}
+
+
{round.description}
+
+
+
+
+ );
+};
diff --git a/packages/interface/src/features/rounds/components/RoundsList.tsx b/packages/interface/src/features/rounds/components/RoundsList.tsx
new file mode 100644
index 00000000..4d4b35a8
--- /dev/null
+++ b/packages/interface/src/features/rounds/components/RoundsList.tsx
@@ -0,0 +1,16 @@
+import { useRound } from "~/contexts/Round";
+
+import { RoundItem } from "./RoundItem";
+
+/// TODO: change to InfiniteLoading after loading rounds from registry contract and make search from trpc service
+export const RoundsList = (): JSX.Element => {
+ const { rounds } = useRound();
+
+ return (
+
+ {rounds.map((round) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/interface/src/features/rounds/types/index.ts b/packages/interface/src/features/rounds/types/index.ts
new file mode 100644
index 00000000..25a13431
--- /dev/null
+++ b/packages/interface/src/features/rounds/types/index.ts
@@ -0,0 +1,12 @@
+import { z } from "zod";
+
+export const RoundSchema = z.object({
+ roundId: z.string(),
+ description: z.string(),
+ startsAt: z.number(),
+ registrationEndsAt: z.number(),
+ votingEndsAt: z.number(),
+ tallyURL: z.string().optional(),
+});
+
+export type Round = z.infer;
diff --git a/packages/interface/src/features/voters/hooks/useApproveVoters.ts b/packages/interface/src/features/voters/hooks/useApproveVoters.ts
index eaf7ff3c..b24daf14 100644
--- a/packages/interface/src/features/voters/hooks/useApproveVoters.ts
+++ b/packages/interface/src/features/voters/hooks/useApproveVoters.ts
@@ -1,7 +1,7 @@
import { type Transaction } from "@ethereum-attestation-service/eas-sdk";
import { type UseMutationResult, useMutation } from "@tanstack/react-query";
-import { config, eas } from "~/config";
+import { eas } from "~/config";
import { useAttest } from "~/hooks/useEAS";
import { useEthersSigner } from "~/hooks/useEthersSigner";
import { createAttestation } from "~/lib/eas/createAttestation";
@@ -25,11 +25,12 @@ export function useApproveVoters(options: {
throw new Error("Connect wallet first");
}
+ /// TODO: should be changed to event name instead of roundId
const attestations = await Promise.all(
voters.map((recipient) =>
createAttestation(
{
- values: { type: "voter", round: config.roundId },
+ values: { type: "voter" },
schemaUID: eas.schemas.approval,
recipient,
},
diff --git a/packages/interface/src/hooks/useResults.ts b/packages/interface/src/hooks/useResults.ts
index a807cd25..a68027fe 100644
--- a/packages/interface/src/hooks/useResults.ts
+++ b/packages/interface/src/hooks/useResults.ts
@@ -1,44 +1,50 @@
import { config } from "~/config";
import { api } from "~/utils/api";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
import type { UseTRPCInfiniteQueryResult, UseTRPCQueryResult } from "@trpc/react-query/shared";
import type { IGetPollData } from "maci-cli/sdk";
import type { Attestation } from "~/utils/types";
export function useResults(
+ roundId: string,
pollData?: IGetPollData,
): UseTRPCQueryResult<{ averageVotes: number; projects: Record }, unknown> {
- const appState = useAppState();
+ const roundState = useRoundState(roundId);
- return api.results.votes.useQuery({ pollId: pollData?.id.toString() }, { enabled: appState === EAppState.RESULTS });
+ return api.results.votes.useQuery(
+ { roundId, pollId: pollData?.id.toString() },
+ { enabled: roundState === ERoundState.RESULTS },
+ );
}
const seed = 0;
export function useProjectsResults(
+ roundId: string,
pollData?: IGetPollData,
): UseTRPCInfiniteQueryResult {
return api.results.projects.useInfiniteQuery(
- { limit: config.pageSize, seed, pollId: pollData?.id.toString() },
+ { roundId, limit: config.pageSize, seed, pollId: pollData?.id.toString() },
{
getNextPageParam: (_, pages) => pages.length,
},
);
}
-export function useProjectCount(): UseTRPCQueryResult<{ count: number }, unknown> {
- return api.projects.count.useQuery();
+export function useProjectCount(roundId: string): UseTRPCQueryResult<{ count: number }, unknown> {
+ return api.projects.count.useQuery({ roundId });
}
export function useProjectResults(
id: string,
+ roundId: string,
pollData?: IGetPollData,
): UseTRPCQueryResult<{ amount: number }, unknown> {
- const appState = useAppState();
+ const appState = useRoundState(roundId);
return api.results.project.useQuery(
- { id, pollId: pollData?.id.toString() },
- { enabled: appState === EAppState.RESULTS },
+ { id, roundId, pollId: pollData?.id.toString() },
+ { enabled: appState === ERoundState.RESULTS },
);
}
diff --git a/packages/interface/src/layouts/AdminLayout.tsx b/packages/interface/src/layouts/AdminLayout.tsx
index 90d4dcb2..32dd94fd 100644
--- a/packages/interface/src/layouts/AdminLayout.tsx
+++ b/packages/interface/src/layouts/AdminLayout.tsx
@@ -1,19 +1,10 @@
import { InvalidAdmin } from "~/features/admin/components/InvalidAdmin";
import { useIsAdmin } from "~/hooks/useIsAdmin";
-import type { ReactNode, PropsWithChildren } from "react";
-
-import { type LayoutProps } from "./BaseLayout";
import { Layout } from "./DefaultLayout";
+import { IAdminLayoutProps } from "./types";
-type Props = PropsWithChildren<
- {
- sidebar?: "left" | "right";
- sidebarComponent?: ReactNode;
- } & LayoutProps
->;
-
-export const AdminLayout = ({ children = null, ...props }: Props): JSX.Element => {
+export const AdminLayout = ({ children = null, ...props }: IAdminLayoutProps): JSX.Element => {
const isAdmin = useIsAdmin();
if (isAdmin) {
return {children} ;
diff --git a/packages/interface/src/layouts/BaseLayout.tsx b/packages/interface/src/layouts/BaseLayout.tsx
index 21c41b0a..e44b9e12 100644
--- a/packages/interface/src/layouts/BaseLayout.tsx
+++ b/packages/interface/src/layouts/BaseLayout.tsx
@@ -2,15 +2,7 @@ import clsx from "clsx";
import Head from "next/head";
import { useRouter } from "next/router";
import { useTheme } from "next-themes";
-import {
- type ReactNode,
- type PropsWithChildren,
- createContext,
- useContext,
- useEffect,
- useCallback,
- useMemo,
-} from "react";
+import { type PropsWithChildren, createContext, useContext, useEffect, useCallback, useMemo } from "react";
import { tv } from "tailwind-variants";
import { useAccount } from "wagmi";
@@ -19,6 +11,8 @@ import { createComponent } from "~/components/ui";
import { metadata } from "~/config";
import { useMaci } from "~/contexts/Maci";
+import type { IBaseLayoutProps } from "./types";
+
const Context = createContext({ eligibilityCheck: false, showBallot: false });
const MainContainer = createComponent(
@@ -39,7 +33,7 @@ const MainContainer = createComponent(
export const useLayoutOptions = (): { eligibilityCheck: boolean; showBallot: boolean } => useContext(Context);
-const Sidebar = ({ side = undefined, ...props }: { side?: "left" | "right" } & PropsWithChildren) => (
+const Sidebar = ({ side = undefined, ...props }: PropsWithChildren<{ side?: "left" | "right" }>) => (
);
-export interface LayoutProps {
- title?: string;
- requireAuth?: boolean;
- requireRegistration?: boolean;
- eligibilityCheck?: boolean;
- showBallot?: boolean;
- type?: string;
-}
-
export const BaseLayout = ({
header = null,
title = "",
@@ -70,13 +55,7 @@ export const BaseLayout = ({
showBallot = false,
type = undefined,
children = null,
-}: PropsWithChildren<
- {
- sidebar?: "left" | "right";
- sidebarComponent?: ReactNode;
- header?: ReactNode;
- } & LayoutProps
->): JSX.Element => {
+}: IBaseLayoutProps): JSX.Element => {
const { theme } = useTheme();
const router = useRouter();
const { address, isConnecting } = useAccount();
diff --git a/packages/interface/src/layouts/DefaultLayout.tsx b/packages/interface/src/layouts/DefaultLayout.tsx
index 562d09f2..871d39f0 100644
--- a/packages/interface/src/layouts/DefaultLayout.tsx
+++ b/packages/interface/src/layouts/DefaultLayout.tsx
@@ -1,5 +1,5 @@
import { GatekeeperTrait } from "maci-cli/sdk";
-import { type ReactNode, type PropsWithChildren, useMemo } from "react";
+import { useMemo } from "react";
import { useAccount } from "wagmi";
import Header from "~/components/Header";
@@ -9,53 +9,65 @@ import { config } from "~/config";
import { useBallot } from "~/contexts/Ballot";
import { useMaci } from "~/contexts/Maci";
import { SubmitBallotButton } from "~/features/ballot/components/SubmitBallotButton";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
-import { BaseLayout, type LayoutProps } from "./BaseLayout";
+import type { ILayoutProps } from "./types";
-interface ILayoutProps extends PropsWithChildren
{
- sidebar?: "left" | "right";
- sidebarComponent?: ReactNode;
- showInfo?: boolean;
- showSubmitButton?: boolean;
-}
+import { BaseLayout } from "./BaseLayout";
export const Layout = ({ children = null, ...props }: ILayoutProps): JSX.Element => {
const { address } = useAccount();
- const appState = useAppState();
+ const roundState = useRoundState(props.roundId ?? "");
const { ballot } = useBallot();
const { isRegistered, gatekeeperTrait } = useMaci();
const navLinks = useMemo(() => {
- const links = [
- {
- href: "/projects",
+ const links = [];
+
+ if (roundState !== ERoundState.DEFAULT) {
+ links.push({
+ href: `/rounds/${props.roundId}`,
children: "Projects",
- },
- ];
+ });
+ }
- if (appState === EAppState.VOTING && isRegistered) {
+ if (roundState === ERoundState.VOTING && isRegistered) {
links.push({
- href: "/ballot",
+ href: `/rounds/${props.roundId}/ballot`,
children: "My Ballot",
});
}
- if ((appState === EAppState.TALLYING || appState === EAppState.RESULTS) && ballot.published) {
+ if (
+ (roundState === ERoundState.TALLYING || roundState === ERoundState.RESULTS) &&
+ ballot.published &&
+ isRegistered
+ ) {
links.push({
- href: "/ballot/confirmation",
+ href: `/rounds/${props.roundId}/ballot/confirmation`,
children: "Submitted Ballot",
});
}
- if (appState === EAppState.RESULTS) {
+ if (roundState === ERoundState.RESULTS) {
links.push({
- href: "/stats",
+ href: `/rounds/${props.roundId}/stats`,
children: "Stats",
});
}
+ if (config.admin === address! && props.roundId) {
+ links.push(
+ ...[
+ {
+ href: `/rounds/${props.roundId}/applications`,
+ children: "Applications",
+ },
+ ],
+ );
+ }
+
if (config.admin === address!) {
links.push(
...[
@@ -74,12 +86,16 @@ export const Layout = ({ children = null, ...props }: ILayoutProps): JSX.Element
href: "/voters",
children: "Voters",
},
+ {
+ href: "/coordinator",
+ children: "Coordinator",
+ },
],
);
}
return links;
- }, [ballot.published, appState, isRegistered, address]);
+ }, [ballot.published, roundState, isRegistered, address]);
return (
}>
@@ -92,7 +108,8 @@ export const LayoutWithSidebar = ({ ...props }: ILayoutProps): JSX.Element => {
const { isRegistered } = useMaci();
const { address } = useAccount();
const { ballot } = useBallot();
- const appState = useAppState();
+ const roundId = props.roundId ?? "";
+ const roundState = useRoundState(roundId);
const { showInfo, showBallot, showSubmitButton } = props;
@@ -102,15 +119,16 @@ export const LayoutWithSidebar = ({ ...props }: ILayoutProps): JSX.Element => {
sidebarComponent={
{showSubmitButton && ballot.votes.length > 0 && (
-
+
{
+ sidebar?: "left" | "right";
+ sidebarComponent?: ReactNode;
+ header?: ReactNode;
+}
+
+export interface ILayoutProps extends PropsWithChildren {
+ sidebar?: "left" | "right";
+ sidebarComponent?: ReactNode;
+ showInfo?: boolean;
+ showSubmitButton?: boolean;
+ roundId?: string;
+}
+
+export interface IAdminLayoutProps extends PropsWithChildren {
+ sidebar?: "left" | "right";
+ sidebarComponent?: ReactNode;
+ roundId?: string;
+}
diff --git a/packages/interface/src/pages/applications/index.tsx b/packages/interface/src/pages/applications/index.tsx
deleted file mode 100644
index b54a5200..00000000
--- a/packages/interface/src/pages/applications/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { ApplicationsToApprove } from "~/features/applications/components/ApplicationsToApprove";
-import { AdminLayout } from "~/layouts/AdminLayout";
-
-const ApplicationsPage = (): JSX.Element => (
-
-
-
-);
-
-export default ApplicationsPage;
diff --git a/packages/interface/src/pages/coordinator/index.tsx b/packages/interface/src/pages/coordinator/index.tsx
new file mode 100644
index 00000000..0a3995d8
--- /dev/null
+++ b/packages/interface/src/pages/coordinator/index.tsx
@@ -0,0 +1,5 @@
+import { Layout } from "~/layouts/DefaultLayout";
+
+const CoordinatorPage = (): JSX.Element => This is the coordinator page. ;
+
+export default CoordinatorPage;
diff --git a/packages/interface/src/pages/index.tsx b/packages/interface/src/pages/index.tsx
index 46f66b79..b0ec4ca8 100644
--- a/packages/interface/src/pages/index.tsx
+++ b/packages/interface/src/pages/index.tsx
@@ -1,15 +1,57 @@
-import { type GetServerSideProps } from "next";
+import { useAccount } from "wagmi";
+import { JoinButton } from "~/components/JoinButton";
+import { Button } from "~/components/ui/Button";
+import { Heading } from "~/components/ui/Heading";
+import { config } from "~/config";
+import { useMaci } from "~/contexts/Maci";
+import { useRound } from "~/contexts/Round";
+import { FAQList } from "~/features/home/components/FaqList";
+import { RoundsList } from "~/features/rounds/components/RoundsList";
+import { useIsAdmin } from "~/hooks/useIsAdmin";
import { Layout } from "~/layouts/DefaultLayout";
-const SignupPage = (): JSX.Element => ... ;
+const HomePage = (): JSX.Element => {
+ const { isConnected } = useAccount();
+ const { isRegistered } = useMaci();
+ const isAdmin = useIsAdmin();
+ const { rounds } = useRound();
-export default SignupPage;
+ return (
+
+
+
+ {config.eventName}
+
-export const getServerSideProps: GetServerSideProps = async () =>
- Promise.resolve({
- redirect: {
- destination: "/signup",
- permanent: false,
- },
- });
+
+ {config.eventDescription}
+
+
+ {!isConnected &&
Connect your wallet to get started.
}
+
+ {isConnected && !isAdmin && !isRegistered &&
}
+
+ {isConnected && isAdmin && (
+
+
Configure and deploy your contracts to get started.
+
+
+ Get Started
+
+
+ )}
+
+ {isConnected && !isAdmin && rounds.length === 0 && (
+
There are no rounds deployed.
+ )}
+
+ {rounds.length > 0 &&
}
+
+
+
+
+ );
+};
+
+export default HomePage;
diff --git a/packages/interface/src/pages/info/index.tsx b/packages/interface/src/pages/info/index.tsx
deleted file mode 100644
index 434a1b8c..00000000
--- a/packages/interface/src/pages/info/index.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { Heading } from "~/components/ui/Heading";
-import { config } from "~/config";
-import { Layout } from "~/layouts/DefaultLayout";
-import { cn } from "~/utils/classNames";
-import { formatDate } from "~/utils/time";
-
-const steps = [
- {
- label: "Registration",
- date: config.startsAt,
- },
- {
- label: "Voting",
- date: config.registrationEndsAt,
- },
- {
- label: "Tallying",
- date: undefined,
- },
- {
- label: "Distribution",
- date: undefined,
- },
-];
-
-const InfoPage = (): JSX.Element => {
- const { progress, currentStepIndex } = calculateProgress(steps);
-
- return (
-
-
-
-
- {steps.map((step, i) => (
-
-
- {step.label}
-
-
- {step.date instanceof Date && !Number.isNaN(step.date) &&
{formatDate(step.date)}
}
-
- ))}
-
-
- );
-};
-
-export default InfoPage;
-
-function calculateProgress(items: { label: string; date?: Date }[]) {
- const now = Number(new Date());
-
- let currentStepIndex = items.findIndex(
- (step, index) => now < Number(step.date) && (index === 0 || now >= Number(items[index - 1]?.date)),
- );
-
- if (currentStepIndex === -1) {
- currentStepIndex = items.length;
- }
-
- let progress = 0;
-
- if (currentStepIndex > 0) {
- // Calculate progress for completed segments
- for (let i = 0; i < currentStepIndex - 1; i += 1) {
- progress += 1 / (items.length - 1);
- }
-
- // Calculate progress within the current segment
- const segmentStart = currentStepIndex === 0 ? 0 : Number(items[currentStepIndex - 1]?.date);
- const segmentEnd = Number(items[currentStepIndex]?.date);
- const segmentDuration = segmentEnd - segmentStart;
- const timeElapsedInSegment = now - segmentStart;
-
- progress += Math.min(timeElapsedInSegment, segmentDuration) / segmentDuration / (items.length - 1);
- }
-
- progress = Math.min(Math.max(progress, 0), 1);
-
- return { progress, currentStepIndex };
-}
diff --git a/packages/interface/src/pages/projects/index.tsx b/packages/interface/src/pages/projects/index.tsx
deleted file mode 100644
index 60c88134..00000000
--- a/packages/interface/src/pages/projects/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Projects } from "~/features/projects/components/Projects";
-import { LayoutWithSidebar } from "~/layouts/DefaultLayout";
-
-const ProjectsPage = (): JSX.Element => (
-
-
-
-);
-
-export default ProjectsPage;
diff --git a/packages/interface/src/pages/projects/results.tsx b/packages/interface/src/pages/projects/results.tsx
deleted file mode 100644
index 52defe6c..00000000
--- a/packages/interface/src/pages/projects/results.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { ProjectsResults } from "~/features/projects/components/ProjectsResults";
-import { Layout } from "~/layouts/DefaultLayout";
-
-const ProjectsResultsPage = (): JSX.Element => (
-
-
-
-);
-
-export default ProjectsResultsPage;
diff --git a/packages/interface/src/pages/projects/[projectId]/Project.tsx b/packages/interface/src/pages/rounds/[roundId]/[projectId]/Project.tsx
similarity index 60%
rename from packages/interface/src/pages/projects/[projectId]/Project.tsx
rename to packages/interface/src/pages/rounds/[roundId]/[projectId]/Project.tsx
index f05cd1c7..5c057895 100644
--- a/packages/interface/src/pages/projects/[projectId]/Project.tsx
+++ b/packages/interface/src/pages/rounds/[roundId]/[projectId]/Project.tsx
@@ -1,33 +1,34 @@
-import { type GetServerSideProps } from "next";
-
import { ReviewBar } from "~/features/applications/components/ReviewBar";
import ProjectDetails from "~/features/projects/components/ProjectDetails";
import { useProjectById } from "~/features/projects/hooks/useProjects";
import { LayoutWithSidebar } from "~/layouts/DefaultLayout";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
+
+import type { GetServerSideProps } from "next";
export interface IProjectDetailsProps {
+ roundId: string;
projectId?: string;
}
-const ProjectDetailsPage = ({ projectId = "" }: IProjectDetailsProps): JSX.Element => {
+const ProjectDetailsPage = ({ roundId, projectId = "" }: IProjectDetailsProps): JSX.Element => {
const projects = useProjectById(projectId);
const { name } = projects.data?.[0] ?? {};
- const appState = useAppState();
+ const appState = useRoundState(roundId);
return (
- {appState === EAppState.APPLICATION && }
+ {appState === ERoundState.APPLICATION && }
-
+
);
};
export default ProjectDetailsPage;
-export const getServerSideProps: GetServerSideProps = async ({ query: { projectId } }) =>
+export const getServerSideProps: GetServerSideProps = async ({ query: { projectId, roundId } }) =>
Promise.resolve({
- props: { projectId },
+ props: { projectId, roundId },
});
diff --git a/packages/interface/src/pages/projects/[projectId]/index.tsx b/packages/interface/src/pages/rounds/[roundId]/[projectId]/index.tsx
similarity index 100%
rename from packages/interface/src/pages/projects/[projectId]/index.tsx
rename to packages/interface/src/pages/rounds/[roundId]/[projectId]/index.tsx
diff --git a/packages/interface/src/pages/applications/confirmation.tsx b/packages/interface/src/pages/rounds/[roundId]/applications/confirmation.tsx
similarity index 80%
rename from packages/interface/src/pages/applications/confirmation.tsx
rename to packages/interface/src/pages/rounds/[roundId]/applications/confirmation.tsx
index 7c9817fc..ac2c7102 100644
--- a/packages/interface/src/pages/applications/confirmation.tsx
+++ b/packages/interface/src/pages/rounds/[roundId]/applications/confirmation.tsx
@@ -8,11 +8,15 @@ import { Heading } from "~/components/ui/Heading";
import { useApplicationByTxHash } from "~/features/applications/hooks/useApplicationByTxHash";
import { ProjectItem } from "~/features/projects/components/ProjectItem";
import { Layout } from "~/layouts/DefaultLayout";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
-const ConfirmProjectPage = (): JSX.Element => {
- const state = useAppState();
+interface IConfirmProjectPageProps {
+ roundId: string;
+}
+
+const ConfirmProjectPage = ({ roundId }: IConfirmProjectPageProps): JSX.Element => {
+ const state = useRoundState(roundId);
const searchParams = useSearchParams();
const txHash = useMemo(() => searchParams.get("txHash"), [searchParams]);
@@ -38,11 +42,11 @@ const ConfirmProjectPage = (): JSX.Element => {
Applications can be edited and approved until the Application period ends.
- {state !== EAppState.APPLICATION && }
+ {state !== ERoundState.APPLICATION && }
{attestation && (
-
+
)}
diff --git a/packages/interface/src/pages/rounds/[roundId]/applications/index.tsx b/packages/interface/src/pages/rounds/[roundId]/applications/index.tsx
new file mode 100644
index 00000000..e3417942
--- /dev/null
+++ b/packages/interface/src/pages/rounds/[roundId]/applications/index.tsx
@@ -0,0 +1,21 @@
+import { ApplicationsToApprove } from "~/features/applications/components/ApplicationsToApprove";
+import { AdminLayout } from "~/layouts/AdminLayout";
+
+import type { GetServerSideProps } from "next";
+
+interface IApplicationsPageProps {
+ roundId: string;
+}
+
+const ApplicationsPage = ({ roundId }: IApplicationsPageProps): JSX.Element => (
+
+
+
+);
+
+export default ApplicationsPage;
+
+export const getServerSideProps: GetServerSideProps = async ({ query: { roundId } }) =>
+ Promise.resolve({
+ props: { roundId },
+ });
diff --git a/packages/interface/src/pages/applications/new.tsx b/packages/interface/src/pages/rounds/[roundId]/applications/new.tsx
similarity index 79%
rename from packages/interface/src/pages/applications/new.tsx
rename to packages/interface/src/pages/rounds/[roundId]/applications/new.tsx
index bf41121f..b13de9a5 100644
--- a/packages/interface/src/pages/applications/new.tsx
+++ b/packages/interface/src/pages/rounds/[roundId]/applications/new.tsx
@@ -4,11 +4,15 @@ import { Alert } from "~/components/ui/Alert";
import { Heading } from "~/components/ui/Heading";
import { ApplicationForm } from "~/features/applications/components/ApplicationForm";
import { Layout } from "~/layouts/DefaultLayout";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
-const NewProjectPage = (): JSX.Element => {
- const state = useAppState();
+interface INewProjectPageProps {
+ roundId: string;
+}
+
+const NewProjectPage = ({ roundId }: INewProjectPageProps): JSX.Element => {
+ const state = useRoundState(roundId);
return (
@@ -34,10 +38,10 @@ const NewProjectPage = (): JSX.Element => {
Applications can be edited and approved until the Application period ends.
- {state !== EAppState.APPLICATION ? (
+ {state !== ERoundState.APPLICATION ? (
) : (
-
+
)}
diff --git a/packages/interface/src/pages/ballot/confirmation.tsx b/packages/interface/src/pages/rounds/[roundId]/ballot/confirmation.tsx
similarity index 72%
rename from packages/interface/src/pages/ballot/confirmation.tsx
rename to packages/interface/src/pages/rounds/[roundId]/ballot/confirmation.tsx
index f5ad70e6..5d4859e9 100644
--- a/packages/interface/src/pages/ballot/confirmation.tsx
+++ b/packages/interface/src/pages/rounds/[roundId]/ballot/confirmation.tsx
@@ -6,7 +6,11 @@ import { useBallot } from "~/contexts/Ballot";
import { BallotConfirmation } from "~/features/ballot/components/BallotConfirmation";
import { Layout } from "~/layouts/DefaultLayout";
-const BallotConfirmationPage = (): JSX.Element | null => {
+interface IBallotConfirmationPageProps {
+ roundId: string;
+}
+
+const BallotConfirmationPage = ({ roundId }: IBallotConfirmationPageProps): JSX.Element | null => {
const [isLoading, setIsLoading] = useState
(true);
const { ballot, isLoading: isBallotLoading } = useBallot();
@@ -28,7 +32,11 @@ const BallotConfirmationPage = (): JSX.Element | null => {
manageDisplay();
}, [manageDisplay]);
- return {isLoading ? : } ;
+ return (
+
+ {isLoading ? : }
+
+ );
};
export default BallotConfirmationPage;
diff --git a/packages/interface/src/pages/ballot/index.tsx b/packages/interface/src/pages/rounds/[roundId]/ballot/index.tsx
similarity index 77%
rename from packages/interface/src/pages/ballot/index.tsx
rename to packages/interface/src/pages/rounds/[roundId]/ballot/index.tsx
index 9957677c..82e48bd2 100644
--- a/packages/interface/src/pages/ballot/index.tsx
+++ b/packages/interface/src/pages/rounds/[roundId]/ballot/index.tsx
@@ -15,10 +15,16 @@ import { AllocationFormWrapper } from "~/features/ballot/components/AllocationFo
import { BallotSchema } from "~/features/ballot/types";
import { LayoutWithSidebar } from "~/layouts/DefaultLayout";
import { formatNumber } from "~/utils/formatNumber";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
-const ClearBallot = (): JSX.Element | null => {
+import type { GetServerSideProps } from "next";
+
+interface IClearBallotProps {
+ roundId: string;
+}
+
+const ClearBallot = ({ roundId }: IClearBallotProps): JSX.Element | null => {
const form = useFormContext();
const [isOpen, setOpen] = useState(false);
const { deleteBallot } = useBallot();
@@ -27,7 +33,7 @@ const ClearBallot = (): JSX.Element | null => {
setOpen(true);
}, [setOpen]);
- if ([EAppState.TALLYING, EAppState.RESULTS].includes(useAppState())) {
+ if ([ERoundState.TALLYING, ERoundState.RESULTS].includes(useRoundState(roundId))) {
return null;
}
@@ -81,8 +87,12 @@ const EmptyBallot = (): JSX.Element => (
);
-const BallotAllocationForm = (): JSX.Element => {
- const appState = useAppState();
+interface IBallotAllocationFormProps {
+ roundId: string;
+}
+
+const BallotAllocationForm = ({ roundId }: IBallotAllocationFormProps): JSX.Element => {
+ const roundState = useRoundState(roundId);
const { ballot, sumBallot } = useBallot();
const { initialVoiceCredits } = useMaci();
@@ -102,12 +112,12 @@ const BallotAllocationForm = (): JSX.Element => {
)}
- {ballot.votes.length ? : null}
+ {ballot.votes.length ? : null}
{ballot.votes.length ? (
-
+
) : (
)}
@@ -123,11 +133,15 @@ const BallotAllocationForm = (): JSX.Element => {
);
};
-const BallotPage = (): JSX.Element => {
+interface IBallotPageProps {
+ roundId: string;
+}
+
+const BallotPage = ({ roundId }: IBallotPageProps): JSX.Element => {
const { address, isConnecting } = useAccount();
const { ballot, sumBallot } = useBallot();
const router = useRouter();
- const appState = useAppState();
+ const roundState = useRoundState(roundId);
useEffect(() => {
if (!address && !isConnecting) {
@@ -140,14 +154,14 @@ const BallotPage = (): JSX.Element => {
}, [sumBallot]);
return (
-
- {appState === EAppState.VOTING && (
+
+ {roundState === ERoundState.VOTING && (
)}
- {appState !== EAppState.VOTING && (
+ {roundState !== ERoundState.VOTING && (
You can only vote during the voting period.
)}
@@ -155,3 +169,8 @@ const BallotPage = (): JSX.Element => {
};
export default BallotPage;
+
+export const getServerSideProps: GetServerSideProps = async ({ query: { roundId } }) =>
+ Promise.resolve({
+ props: { roundId },
+ });
diff --git a/packages/interface/src/pages/rounds/[roundId]/index.tsx b/packages/interface/src/pages/rounds/[roundId]/index.tsx
new file mode 100644
index 00000000..674fea0f
--- /dev/null
+++ b/packages/interface/src/pages/rounds/[roundId]/index.tsx
@@ -0,0 +1,21 @@
+import { Projects } from "~/features/rounds/components/Projects";
+import { LayoutWithSidebar } from "~/layouts/DefaultLayout";
+
+import type { GetServerSideProps } from "next";
+
+export interface IRoundsPageProps {
+ roundId: string;
+}
+
+const RoundsPage = ({ roundId }: IRoundsPageProps): JSX.Element => (
+
+
+
+);
+
+export default RoundsPage;
+
+export const getServerSideProps: GetServerSideProps = async ({ query: { roundId } }) =>
+ Promise.resolve({
+ props: { roundId },
+ });
diff --git a/packages/interface/src/pages/stats/index.tsx b/packages/interface/src/pages/rounds/[roundId]/stats/index.tsx
similarity index 73%
rename from packages/interface/src/pages/stats/index.tsx
rename to packages/interface/src/pages/rounds/[roundId]/stats/index.tsx
index f28e9274..bb6fca98 100644
--- a/packages/interface/src/pages/stats/index.tsx
+++ b/packages/interface/src/pages/rounds/[roundId]/stats/index.tsx
@@ -6,13 +6,15 @@ import { useAccount } from "wagmi";
import { ConnectButton } from "~/components/ConnectButton";
import { Alert } from "~/components/ui/Alert";
import { Heading } from "~/components/ui/Heading";
-import { config } from "~/config";
import { useMaci } from "~/contexts/Maci";
+import { useRound } from "~/contexts/Round";
import { useProjectCount, useProjectsResults, useResults } from "~/hooks/useResults";
import { Layout } from "~/layouts/DefaultLayout";
import { formatNumber } from "~/utils/formatNumber";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
+import { useRoundState } from "~/utils/state";
+import { ERoundState } from "~/utils/types";
+
+import type { GetServerSideProps } from "next";
const ResultsChart = dynamic(async () => import("~/features/results/components/Chart"), { ssr: false });
@@ -26,11 +28,15 @@ const Stat = ({ title, children = null }: PropsWithChildren<{ title: string }>)
);
-const Stats = () => {
+interface IStatsProps {
+ roundId: string;
+}
+
+const Stats = ({ roundId }: IStatsProps) => {
const { isLoading, pollData } = useMaci();
- const results = useResults(pollData);
- const count = useProjectCount();
- const { data: projectsResults } = useProjectsResults(pollData);
+ const results = useResults(roundId, pollData);
+ const count = useProjectCount(roundId);
+ const { data: projectsResults } = useProjectsResults(roundId, pollData);
const { isConnected } = useAccount();
const { averageVotes, projects = {} } = results.data ?? {};
@@ -87,18 +93,24 @@ const Stats = () => {
);
};
-const StatsPage = (): JSX.Element => {
- const appState = useAppState();
- const duration = config.resultsAt && differenceInDays(config.resultsAt, new Date());
+interface IStatsPageProps {
+ roundId: string;
+}
+
+const StatsPage = ({ roundId }: IStatsPageProps): JSX.Element => {
+ const roundState = useRoundState(roundId);
+ const { getRound } = useRound();
+ const round = getRound(roundId);
+ const duration = round?.votingEndsAt && differenceInDays(round.votingEndsAt, new Date());
return (
-
+
Stats
- {appState === EAppState.RESULTS ? (
-
+ {roundState === ERoundState.RESULTS ? (
+
) : (
The results will be revealed in {duration && duration > 0 ? duration : 0}
@@ -110,3 +122,8 @@ const StatsPage = (): JSX.Element => {
};
export default StatsPage;
+
+export const getServerSideProps: GetServerSideProps = async ({ query: { roundId } }) =>
+ Promise.resolve({
+ props: { roundId },
+ });
diff --git a/packages/interface/src/pages/signup/index.tsx b/packages/interface/src/pages/signup/index.tsx
deleted file mode 100644
index 4e6ccc35..00000000
--- a/packages/interface/src/pages/signup/index.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { format } from "date-fns";
-import Link from "next/link";
-import { useAccount } from "wagmi";
-
-import { ConnectButton } from "~/components/ConnectButton";
-import { EligibilityDialog } from "~/components/EligibilityDialog";
-import { Info } from "~/components/Info";
-import { JoinButton } from "~/components/JoinButton";
-import { Button } from "~/components/ui/Button";
-import { Heading } from "~/components/ui/Heading";
-import { config } from "~/config";
-import { useMaci } from "~/contexts/Maci";
-import { FAQList } from "~/features/signup/components/FaqList";
-import { Layout } from "~/layouts/DefaultLayout";
-import { useAppState } from "~/utils/state";
-import { EAppState } from "~/utils/types";
-
-const SignupPage = (): JSX.Element => {
- const { isConnected } = useAccount();
- const { isRegistered } = useMaci();
- const appState = useAppState();
-
- return (
-
-
-
-
-
- {config.eventName}
-
-
-
- {config.roundId.toUpperCase()}
-
-
-
- {config.startsAt && format(config.startsAt, "d MMMM, yyyy")}
-
- -
-
- {config.resultsAt && format(config.resultsAt, "d MMMM, yyyy")}
-
-
- {!isConnected &&
}
-
- {isConnected && appState === EAppState.APPLICATION && (
-
- Start Application
-
- )}
-
- {isConnected && isRegistered && appState === EAppState.VOTING && (
-
- View projects
-
- )}
-
- {isConnected && !isRegistered &&
}
-
-
-
-
-
-
-
-
- );
-};
-
-export default SignupPage;
diff --git a/packages/interface/src/providers/index.tsx b/packages/interface/src/providers/index.tsx
index aa45e622..fdf56b2e 100644
--- a/packages/interface/src/providers/index.tsx
+++ b/packages/interface/src/providers/index.tsx
@@ -8,6 +8,7 @@ import { Toaster } from "~/components/Toaster";
import * as appConfig from "~/config";
import { BallotProvider } from "~/contexts/Ballot";
import { MaciProvider } from "~/contexts/Maci";
+import { RoundProvider } from "~/contexts/Round";
const theme = lightTheme();
@@ -37,11 +38,13 @@ export const Providers = ({ children }: PropsWithChildren): JSX.Element => {
-
- {children}
+
+
+ {children}
-
-
+
+
+
diff --git a/packages/interface/src/server/api/routers/applications.ts b/packages/interface/src/server/api/routers/applications.ts
index c1c90d76..d264da67 100644
--- a/packages/interface/src/server/api/routers/applications.ts
+++ b/packages/interface/src/server/api/routers/applications.ts
@@ -8,23 +8,29 @@ import { createDataFilter } from "~/utils/fetchAttestationsUtils";
export const FilterSchema = z.object({
limit: z.number().default(3 * 8),
cursor: z.number().default(0),
+ roundId: z.string(),
});
export const applicationsRouter = createTRPCRouter({
- approvals: publicProcedure.input(z.object({ ids: z.array(z.string()).optional() })).query(async ({ input }) =>
- fetchAttestations([eas.schemas.approval], {
- where: {
- attester: { equals: config.admin },
- refUID: input.ids ? { in: input.ids } : undefined,
- AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", config.roundId)],
- },
- }),
- ),
- list: publicProcedure.input(FilterSchema).query(async () =>
+ approvals: publicProcedure
+ .input(z.object({ ids: z.array(z.string()).optional(), roundId: z.string() }))
+ .query(async ({ input }) =>
+ fetchAttestations([eas.schemas.approval], {
+ where: {
+ attester: { equals: config.admin },
+ refUID: input.ids ? { in: input.ids } : undefined,
+ AND: [
+ createDataFilter("type", "bytes32", "application"),
+ createDataFilter("round", "bytes32", input.roundId),
+ ],
+ },
+ }),
+ ),
+ list: publicProcedure.input(FilterSchema).query(async ({ input }) =>
fetchAttestations([eas.schemas.metadata], {
orderBy: [{ time: "desc" }],
where: {
- AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", config.roundId)],
+ AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", input.roundId)],
},
}),
),
diff --git a/packages/interface/src/server/api/routers/projects.ts b/packages/interface/src/server/api/routers/projects.ts
index fbb5d2e0..9a2bd4f3 100644
--- a/packages/interface/src/server/api/routers/projects.ts
+++ b/packages/interface/src/server/api/routers/projects.ts
@@ -11,11 +11,11 @@ import { fetchMetadata } from "~/utils/fetchMetadata";
import type { Attestation } from "~/utils/types";
export const projectsRouter = createTRPCRouter({
- count: publicProcedure.query(async () =>
+ count: publicProcedure.input(z.object({ roundId: z.string() })).query(async ({ input }) =>
fetchAttestations([eas.schemas.approval], {
where: {
attester: { equals: config.admin },
- AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", config.roundId)],
+ AND: [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", input.roundId)],
},
}).then((attestations = []) =>
// Handle multiple approvals of an application - group by refUID
@@ -46,7 +46,7 @@ export const projectsRouter = createTRPCRouter({
search: publicProcedure.input(FilterSchema).query(async ({ input }) => {
const filters = [
createDataFilter("type", "bytes32", "application"),
- createDataFilter("round", "bytes32", config.roundId),
+ createDataFilter("round", "bytes32", input.roundId),
];
if (input.search) {
@@ -107,10 +107,10 @@ export const projectsRouter = createTRPCRouter({
.then((projects) => projects.reduce((acc, x) => ({ ...acc, [x.projectId]: x.payoutAddress }), {})),
),
- allApproved: publicProcedure.query(async () => {
+ allApproved: publicProcedure.input(z.object({ roundId: z.string() })).query(async ({ input }) => {
const filters = [
createDataFilter("type", "bytes32", "application"),
- createDataFilter("round", "bytes32", config.roundId),
+ createDataFilter("round", "bytes32", input.roundId),
];
return fetchAttestations([eas.schemas.approval], {
@@ -132,11 +132,8 @@ export const projectsRouter = createTRPCRouter({
}),
});
-export async function getAllApprovedProjects(): Promise {
- const filters = [
- createDataFilter("type", "bytes32", "application"),
- createDataFilter("round", "bytes32", config.roundId),
- ];
+export async function getAllApprovedProjects({ roundId }: { roundId: string }): Promise {
+ const filters = [createDataFilter("type", "bytes32", "application"), createDataFilter("round", "bytes32", roundId)];
return fetchAttestations([eas.schemas.approval], {
where: {
diff --git a/packages/interface/src/server/api/routers/results.ts b/packages/interface/src/server/api/routers/results.ts
index cdb876ac..d231b2ca 100644
--- a/packages/interface/src/server/api/routers/results.ts
+++ b/packages/interface/src/server/api/routers/results.ts
@@ -11,40 +11,45 @@ import { getAllApprovedProjects } from "./projects";
export const resultsRouter = createTRPCRouter({
votes: publicProcedure
- .input(z.object({ pollId: z.string().nullish() }))
- .query(async ({ input }) => calculateMaciResults(input.pollId)),
+ .input(z.object({ roundId: z.string(), pollId: z.string().nullish() }))
+ .query(async ({ input }) => calculateMaciResults(input.roundId, input.pollId)),
project: publicProcedure
- .input(z.object({ id: z.string(), pollId: z.string().nullish() }))
+ .input(z.object({ id: z.string(), roundId: z.string(), pollId: z.string().nullish() }))
.query(async ({ input }) => {
- const { projects } = await calculateMaciResults(input.pollId);
+ const { projects } = await calculateMaciResults(input.roundId, input.pollId);
return {
amount: projects[input.id]?.votes ?? 0,
};
}),
- projects: publicProcedure.input(FilterSchema.extend({ pollId: z.string().nullish() })).query(async ({ input }) => {
- const { projects } = await calculateMaciResults(input.pollId);
-
- const sortedIDs = Object.entries(projects)
- .sort((a, b) => b[1].votes - a[1].votes)
- .map(([id]) => id)
- .slice(input.cursor * input.limit, input.cursor * input.limit + input.limit);
-
- return fetchAttestations([eas.schemas.metadata], {
- where: {
- id: { in: sortedIDs },
- },
- }).then((attestations) =>
- // Results aren't returned from EAS in the same order as the `where: { in: sortedIDs }`
- // Sort the attestations based on the sorted array
- attestations.sort((a, b) => sortedIDs.indexOf(a.id) - sortedIDs.indexOf(b.id)),
- );
- }),
+ projects: publicProcedure
+ .input(FilterSchema.extend({ roundId: z.string(), pollId: z.string().nullish() }))
+ .query(async ({ input }) => {
+ const { projects } = await calculateMaciResults(input.roundId, input.pollId);
+
+ const sortedIDs = Object.entries(projects)
+ .sort((a, b) => b[1].votes - a[1].votes)
+ .map(([id]) => id)
+ .slice(input.cursor * input.limit, input.cursor * input.limit + input.limit);
+
+ return fetchAttestations([eas.schemas.metadata], {
+ where: {
+ id: { in: sortedIDs },
+ },
+ }).then((attestations) =>
+ // Results aren't returned from EAS in the same order as the `where: { in: sortedIDs }`
+ // Sort the attestations based on the sorted array
+ attestations.sort((a, b) => sortedIDs.indexOf(a.id) - sortedIDs.indexOf(b.id)),
+ );
+ }),
});
-export async function calculateMaciResults(pollId?: string | null): Promise<{
+export async function calculateMaciResults(
+ roundId: string,
+ pollId?: string | null,
+): Promise<{
averageVotes: number;
projects: Record;
}> {
@@ -56,7 +61,7 @@ export async function calculateMaciResults(pollId?: string | null): Promise<{
fetch(`${config.tallyUrl}/tally-${pollId}.json`)
.then((res) => res.json() as Promise)
.catch(() => undefined),
- getAllApprovedProjects(),
+ getAllApprovedProjects({ roundId }),
]);
if (!tallyData) {
diff --git a/packages/interface/src/server/api/routers/voters.ts b/packages/interface/src/server/api/routers/voters.ts
index d3f0b2dc..cb8173f8 100644
--- a/packages/interface/src/server/api/routers/voters.ts
+++ b/packages/interface/src/server/api/routers/voters.ts
@@ -1,10 +1,11 @@
import { z } from "zod";
-import { config, eas } from "~/config";
+import { eas } from "~/config";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
import { fetchAttestations, fetchApprovedVoter, fetchApprovedVoterAttestations } from "~/utils/fetchAttestations";
import { createDataFilter } from "~/utils/fetchAttestationsUtils";
+/// TODO: change to filter with event name instead of roundId
export const FilterSchema = z.object({
limit: z.number().default(3 * 8),
cursor: z.number().default(0),
@@ -22,7 +23,7 @@ export const votersRouter = createTRPCRouter({
list: publicProcedure.input(FilterSchema).query(async () =>
fetchAttestations([eas.schemas.approval], {
where: {
- AND: [createDataFilter("type", "bytes32", "voter"), createDataFilter("round", "bytes32", config.roundId)],
+ AND: [createDataFilter("type", "bytes32", "voter")],
},
}),
),
diff --git a/packages/interface/src/utils/fetchAttestations.ts b/packages/interface/src/utils/fetchAttestations.ts
index baaceafb..dfd52630 100644
--- a/packages/interface/src/utils/fetchAttestations.ts
+++ b/packages/interface/src/utils/fetchAttestations.ts
@@ -1,4 +1,4 @@
-import { config, eas } from "~/config";
+import { eas } from "~/config";
import { createCachedFetch } from "./fetch";
import { parseAttestation, createDataFilter } from "./fetchAttestationsUtils";
@@ -6,9 +6,8 @@ import { type AttestationWithMetadata, type AttestationsFilter, type Attestation
const cachedFetch = createCachedFetch({ ttl: 1000 * 60 * 10 });
+/// TODO: add roundId as one of the filter
export async function fetchAttestations(schema: string[], filter?: AttestationsFilter): Promise {
- const startsAt = config.startsAt && Math.floor(+config.startsAt / 1000);
-
return cachedFetch<{ attestations: AttestationWithMetadata[] }>(eas.url, {
method: "POST",
body: JSON.stringify({
@@ -18,7 +17,6 @@ export async function fetchAttestations(schema: string[], filter?: AttestationsF
where: {
schemaId: { in: schema },
revoked: { equals: false },
- time: { gte: startsAt },
...filter?.where,
},
},
diff --git a/packages/interface/src/utils/fetchAttestationsWithoutCache.ts b/packages/interface/src/utils/fetchAttestationsWithoutCache.ts
index 3f328971..7d1d0c93 100644
--- a/packages/interface/src/utils/fetchAttestationsWithoutCache.ts
+++ b/packages/interface/src/utils/fetchAttestationsWithoutCache.ts
@@ -3,9 +3,7 @@ import { config, eas } from "~/config";
import { parseAttestation, createDataFilter } from "./fetchAttestationsUtils";
import { type AttestationWithMetadata, type Attestation, AttestationsQuery } from "./types";
-export async function fetchApprovedApplications(ids?: string[]): Promise {
- const startsAt = config.startsAt && Math.floor(+config.startsAt / 1000);
-
+export async function fetchApprovedApplications(roundId: string, ids?: string[]): Promise {
return fetch(eas.url, {
method: "POST",
headers: {
@@ -17,13 +15,9 @@ export async function fetchApprovedApplications(ids?: string[]): Promise {
+export const useRoundState = (roundId: string): ERoundState => {
const now = new Date();
- const { votingEndsAt, pollData, tallyData } = useMaci();
+ const { getRound } = useRound();
+ const round = getRound(roundId);
- if (config.registrationEndsAt && isAfter(config.registrationEndsAt, now)) {
- return EAppState.APPLICATION;
+ if (!round) {
+ return ERoundState.DEFAULT;
}
- if (isAfter(votingEndsAt, now)) {
- return EAppState.VOTING;
+ if (round.registrationEndsAt && isAfter(round.registrationEndsAt, now)) {
+ return ERoundState.APPLICATION;
}
- if (!pollData?.isMerged || !tallyData) {
- return EAppState.TALLYING;
+ if (round.votingEndsAt && isAfter(round.votingEndsAt, now)) {
+ return ERoundState.VOTING;
}
- return EAppState.RESULTS;
+ if (round.votingEndsAt && isAfter(now, round.votingEndsAt) && !round.tallyURL) {
+ return ERoundState.TALLYING;
+ }
+
+ if (round.tallyURL) {
+ return ERoundState.RESULTS;
+ }
+
+ return ERoundState.DEFAULT;
};
diff --git a/packages/interface/src/utils/time.ts b/packages/interface/src/utils/time.ts
index c1749050..7da2b432 100644
--- a/packages/interface/src/utils/time.ts
+++ b/packages/interface/src/utils/time.ts
@@ -10,3 +10,17 @@ export const calculateTimeLeft = (date: Date): [number, number, number, number]
};
export const formatDate = (date: Date | number): string => format(date, "dd MMM yyyy HH:mm");
+
+export function formatPeriod({ start, end }: { start: Date; end: Date }): string {
+ const fullFormat = "d MMM yyyy";
+
+ if (start.getMonth() === end.getMonth() && start.getFullYear() === end.getFullYear()) {
+ return `${start.getDate()} - ${format(end, fullFormat)}`;
+ }
+
+ if (start.getFullYear() === end.getFullYear()) {
+ return `${format(start, "d MMM")} - ${format(end, fullFormat)}`;
+ }
+
+ return `${format(start, fullFormat)} - ${format(end, fullFormat)}`;
+}
diff --git a/packages/interface/src/utils/types.ts b/packages/interface/src/utils/types.ts
index 02b8cf0a..05f6e390 100644
--- a/packages/interface/src/utils/types.ts
+++ b/packages/interface/src/utils/types.ts
@@ -1,11 +1,12 @@
import { type Address } from "viem";
-export enum EAppState {
+export enum ERoundState {
LOADING = "LOADING",
APPLICATION = "APPLICATION",
VOTING = "VOTING",
TALLYING = "TALLYING",
RESULTS = "RESULTS",
+ DEFAULT = "DEFAULT",
}
export enum EInfoCardState {