From 019a61be86724522f5c5ecec75e8d0d04ab3cd1a Mon Sep 17 00:00:00 2001 From: yu-zhen Date: Wed, 21 Aug 2024 01:08:00 +0800 Subject: [PATCH] feat: add organizer dashboard --- packages/interface/public/arbitrum.svg | 9 ++ packages/interface/public/base.svg | 6 + packages/interface/public/baseSepolia.svg | 6 + packages/interface/public/constant.svg | 4 + packages/interface/public/eas.svg | 9 ++ packages/interface/public/ethereum.svg | 1 + packages/interface/public/freeforall.svg | 3 + packages/interface/public/linea.svg | 12 ++ packages/interface/public/localhost.svg | 20 +++ packages/interface/public/optimism.svg | 9 ++ packages/interface/public/optimismSepolia.svg | 9 ++ packages/interface/public/qf.svg | 3 + packages/interface/public/scroll.svg | 1 + packages/interface/public/scrollSepolia.svg | 1 + packages/interface/public/semaphore.svg | 11 ++ packages/interface/public/sepolia.svg | 24 ++++ packages/interface/public/token.svg | 3 + packages/interface/public/zupass.svg | 10 ++ .../src/components/FormActionButtons.tsx | 93 ++++++++++++ .../interface/src/components/ImageUpload.tsx | 14 +- packages/interface/src/components/Info.tsx | 2 +- .../interface/src/components/RoundInfo.tsx | 14 +- .../Steps.tsx} | 26 ++-- packages/interface/src/components/ui/Form.tsx | 7 + .../src/components/ui/RadioSelect.tsx | 59 ++++++++ packages/interface/src/contexts/Maci.tsx | 5 +- packages/interface/src/contexts/Round.tsx | 54 +++++-- packages/interface/src/contexts/types.ts | 5 +- packages/interface/src/env.js | 2 +- .../features/admin/components/CheckItem.tsx | 18 +++ .../admin/components/DeployContracts.tsx | 83 +++++++++++ .../components/DeployContractsButtons.tsx | 60 ++++++++ .../admin/components/DeployRounds.tsx | 135 ++++++++++++++++++ .../admin/components/DeployRoundsButtons.tsx | 81 +++++++++++ .../ReviewDeployContractsDetails.tsx | 79 ++++++++++ .../components/ReviewDeployRoundDetails.tsx | 55 +++++++ .../components/VoiceCreditProxySelect.tsx | 52 +++++++ .../features/admin/hooks/useDeployRound.ts | 44 ++++++ .../src/features/admin/types/index.ts | 4 + .../components/ApplicationButtons.tsx | 108 ++++---------- .../components/ApplicationForm.tsx | 37 +---- .../src/features/applications/types/index.ts | 6 + .../features/rounds/components/RoundItem.tsx | 4 +- .../features/rounds/components/RoundsList.tsx | 6 +- .../src/features/rounds/types/index.ts | 53 ++++++- .../interface/src/layouts/DefaultLayout.tsx | 8 +- packages/interface/src/pages/admin/index.tsx | 18 +++ .../interface/src/pages/coordinator/index.tsx | 5 - packages/interface/src/pages/index.tsx | 8 +- .../rounds/[roundId]/applications/new.tsx | 7 + packages/interface/tailwind.config.ts | 4 + 51 files changed, 1118 insertions(+), 179 deletions(-) create mode 100644 packages/interface/public/arbitrum.svg create mode 100644 packages/interface/public/base.svg create mode 100644 packages/interface/public/baseSepolia.svg create mode 100644 packages/interface/public/constant.svg create mode 100644 packages/interface/public/eas.svg create mode 100644 packages/interface/public/ethereum.svg create mode 100644 packages/interface/public/freeforall.svg create mode 100644 packages/interface/public/linea.svg create mode 100644 packages/interface/public/localhost.svg create mode 100644 packages/interface/public/optimism.svg create mode 100644 packages/interface/public/optimismSepolia.svg create mode 100644 packages/interface/public/qf.svg create mode 100644 packages/interface/public/scroll.svg create mode 100644 packages/interface/public/scrollSepolia.svg create mode 100644 packages/interface/public/semaphore.svg create mode 100644 packages/interface/public/sepolia.svg create mode 100644 packages/interface/public/token.svg create mode 100644 packages/interface/public/zupass.svg create mode 100644 packages/interface/src/components/FormActionButtons.tsx rename packages/interface/src/{features/applications/components/ApplicationSteps.tsx => components/Steps.tsx} (68%) create mode 100644 packages/interface/src/components/ui/RadioSelect.tsx create mode 100644 packages/interface/src/features/admin/components/CheckItem.tsx create mode 100644 packages/interface/src/features/admin/components/DeployContracts.tsx create mode 100644 packages/interface/src/features/admin/components/DeployContractsButtons.tsx create mode 100644 packages/interface/src/features/admin/components/DeployRounds.tsx create mode 100644 packages/interface/src/features/admin/components/DeployRoundsButtons.tsx create mode 100644 packages/interface/src/features/admin/components/ReviewDeployContractsDetails.tsx create mode 100644 packages/interface/src/features/admin/components/ReviewDeployRoundDetails.tsx create mode 100644 packages/interface/src/features/admin/components/VoiceCreditProxySelect.tsx create mode 100644 packages/interface/src/features/admin/hooks/useDeployRound.ts create mode 100644 packages/interface/src/features/admin/types/index.ts create mode 100644 packages/interface/src/pages/admin/index.tsx delete mode 100644 packages/interface/src/pages/coordinator/index.tsx diff --git a/packages/interface/public/arbitrum.svg b/packages/interface/public/arbitrum.svg new file mode 100644 index 00000000..1e518ab1 --- /dev/null +++ b/packages/interface/public/arbitrum.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/interface/public/base.svg b/packages/interface/public/base.svg new file mode 100644 index 00000000..e924ffde --- /dev/null +++ b/packages/interface/public/base.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/interface/public/baseSepolia.svg b/packages/interface/public/baseSepolia.svg new file mode 100644 index 00000000..e924ffde --- /dev/null +++ b/packages/interface/public/baseSepolia.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/interface/public/constant.svg b/packages/interface/public/constant.svg new file mode 100644 index 00000000..afa5082a --- /dev/null +++ b/packages/interface/public/constant.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/interface/public/eas.svg b/packages/interface/public/eas.svg new file mode 100644 index 00000000..1f072996 --- /dev/null +++ b/packages/interface/public/eas.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/interface/public/ethereum.svg b/packages/interface/public/ethereum.svg new file mode 100644 index 00000000..b5224357 --- /dev/null +++ b/packages/interface/public/ethereum.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/interface/public/freeforall.svg b/packages/interface/public/freeforall.svg new file mode 100644 index 00000000..5ae0f0a0 --- /dev/null +++ b/packages/interface/public/freeforall.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/interface/public/linea.svg b/packages/interface/public/linea.svg new file mode 100644 index 00000000..63103311 --- /dev/null +++ b/packages/interface/public/linea.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/interface/public/localhost.svg b/packages/interface/public/localhost.svg new file mode 100644 index 00000000..6fa85a34 --- /dev/null +++ b/packages/interface/public/localhost.svg @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/interface/public/optimism.svg b/packages/interface/public/optimism.svg new file mode 100644 index 00000000..c1144428 --- /dev/null +++ b/packages/interface/public/optimism.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/interface/public/optimismSepolia.svg b/packages/interface/public/optimismSepolia.svg new file mode 100644 index 00000000..e929f803 --- /dev/null +++ b/packages/interface/public/optimismSepolia.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/interface/public/qf.svg b/packages/interface/public/qf.svg new file mode 100644 index 00000000..5c6272bf --- /dev/null +++ b/packages/interface/public/qf.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/interface/public/scroll.svg b/packages/interface/public/scroll.svg new file mode 100644 index 00000000..d7f63b39 --- /dev/null +++ b/packages/interface/public/scroll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/interface/public/scrollSepolia.svg b/packages/interface/public/scrollSepolia.svg new file mode 100644 index 00000000..d7f63b39 --- /dev/null +++ b/packages/interface/public/scrollSepolia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/interface/public/semaphore.svg b/packages/interface/public/semaphore.svg new file mode 100644 index 00000000..d8e60c76 --- /dev/null +++ b/packages/interface/public/semaphore.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/packages/interface/public/sepolia.svg b/packages/interface/public/sepolia.svg new file mode 100644 index 00000000..1669c499 --- /dev/null +++ b/packages/interface/public/sepolia.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/packages/interface/public/token.svg b/packages/interface/public/token.svg new file mode 100644 index 00000000..2e52d222 --- /dev/null +++ b/packages/interface/public/token.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/interface/public/zupass.svg b/packages/interface/public/zupass.svg new file mode 100644 index 00000000..78c2b42b --- /dev/null +++ b/packages/interface/public/zupass.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/interface/src/components/FormActionButtons.tsx b/packages/interface/src/components/FormActionButtons.tsx new file mode 100644 index 00000000..bfe885a2 --- /dev/null +++ b/packages/interface/src/components/FormActionButtons.tsx @@ -0,0 +1,93 @@ +import { useState, useCallback } from "react"; +import { useAccount } from "wagmi"; + +import { Button, IconButton } from "~/components/ui/Button"; +import { Dialog } from "~/components/ui/Dialog"; +import { Spinner } from "~/components/ui/Spinner"; +import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork"; + +interface IFormActionButtonsProps { + hasNextStep: boolean; + hasPrevStep: boolean; + isUploading: boolean; + isPending: boolean; + onNextStep: () => void; + onBackStep: () => void; +} + +export const FormActionButtons = ({ + hasNextStep, + hasPrevStep, + isUploading, + isPending, + onNextStep, + onBackStep, +}: IFormActionButtonsProps): JSX.Element => { + const { isCorrectNetwork } = useIsCorrectNetwork(); + + const { address } = useAccount(); + + const [showDialog, setShowDialog] = useState(false); + + const handleOnClickNextStep = useCallback( + (event: UIEvent) => { + event.preventDefault(); + + try { + onNextStep(); + } catch (e) { + setShowDialog(true); + } + }, + [onNextStep, setShowDialog], + ); + + const handleOnClickBackStep = useCallback( + (event: UIEvent) => { + event.preventDefault(); + onBackStep(); + }, + [onBackStep], + ); + + const handleOnOpenChange = useCallback(() => { + setShowDialog(false); + }, [setShowDialog]); + + return ( +
+ + + {hasPrevStep && ( + + )} + + {hasNextStep && ( + + )} + + {!hasNextStep && ( + + {isUploading ? "Uploading..." : "Submit"} + + )} +
+ ); +}; diff --git a/packages/interface/src/components/ImageUpload.tsx b/packages/interface/src/components/ImageUpload.tsx index a914a0fb..c4dee714 100644 --- a/packages/interface/src/components/ImageUpload.tsx +++ b/packages/interface/src/components/ImageUpload.tsx @@ -1,7 +1,7 @@ import { useMutation } from "@tanstack/react-query"; import clsx from "clsx"; import { ImageIcon } from "lucide-react"; -import { type ComponentProps, useRef } from "react"; +import { type ComponentProps, useRef, useCallback } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { toast } from "sonner"; @@ -33,17 +33,18 @@ export const ImageUpload = ({ }, }); + const onClick = useCallback(() => { + ref.current?.click(); + }, []); + return ( ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -
ref.current?.click()} - > - +
+
{ const [file] = event.target.files ?? []; if (file) { diff --git a/packages/interface/src/components/Info.tsx b/packages/interface/src/components/Info.tsx index 46ecd5e2..d1e17cba 100644 --- a/packages/interface/src/components/Info.tsx +++ b/packages/interface/src/components/Info.tsx @@ -76,7 +76,7 @@ export const Info = ({ {showRoundInfo && } - {showBallot && } + {showBallot && } {showRoundInfo && roundState === ERoundState.VOTING && } diff --git a/packages/interface/src/components/RoundInfo.tsx b/packages/interface/src/components/RoundInfo.tsx index 6281c17b..0dd35516 100644 --- a/packages/interface/src/components/RoundInfo.tsx +++ b/packages/interface/src/components/RoundInfo.tsx @@ -1,22 +1,20 @@ -import Image from "next/image"; +import clsx from "clsx"; import { Heading } from "~/components/ui/Heading"; -import { config } from "~/config"; interface IRoundInfoProps { roundId: string; + roundLogo?: string; } -export const RoundInfo = ({ roundId }: IRoundInfoProps): JSX.Element => ( +export const RoundInfo = ({ roundId, roundLogo = undefined }: IRoundInfoProps): JSX.Element => (

Round

-
- {config.roundLogo && round logo} +
+ {roundLogo && round logo} - - {roundId} - + {roundId}
); diff --git a/packages/interface/src/features/applications/components/ApplicationSteps.tsx b/packages/interface/src/components/Steps.tsx similarity index 68% rename from packages/interface/src/features/applications/components/ApplicationSteps.tsx rename to packages/interface/src/components/Steps.tsx index 6f5cd096..126d7ada 100644 --- a/packages/interface/src/features/applications/components/ApplicationSteps.tsx +++ b/packages/interface/src/components/Steps.tsx @@ -10,13 +10,17 @@ export enum EStepState { interface IStepCategoryProps { title: string; progress: EStepState; + isLast?: boolean; } -interface IApplicationStepsProps { +interface IStepsProps { step: number; + stepNames: string[]; } -const StepCategory = ({ title, progress }: IStepCategoryProps): JSX.Element => ( +const Interline = (): JSX.Element =>
; + +const StepCategory = ({ title, progress, isLast = false }: IStepCategoryProps): JSX.Element => (
{progress === EStepState.ACTIVE && ( circle-check-blue @@ -29,21 +33,15 @@ const StepCategory = ({ title, progress }: IStepCategoryProps): JSX.Element => ( {progress === EStepState.DEFAULT &&
}

{title}

+ + {!isLast && }
); -const Interline = (): JSX.Element =>
; - -export const ApplicationSteps = ({ step }: IApplicationStepsProps): JSX.Element => ( +export const Steps = ({ step, stepNames }: IStepsProps): JSX.Element => (
- - - - - - - - - + {stepNames.map((name, i) => ( + + ))}
); diff --git a/packages/interface/src/components/ui/Form.tsx b/packages/interface/src/components/ui/Form.tsx index 133f72eb..4025208f 100644 --- a/packages/interface/src/components/ui/Form.tsx +++ b/packages/interface/src/components/ui/Form.tsx @@ -66,6 +66,13 @@ export const ErrorMessage = createComponent("div", tv({ base: "pt-1 text-xs text export const Textarea = createComponent("textarea", tv({ base: [...inputBase, "w-full"] })); +// eslint-disable-next-line react/display-name +export const DateInput = forwardRef(({ ...props }: ComponentPropsWithRef, ref) => ( + + + +)); + export const SearchInput = forwardRef(({ ...props }: ComponentPropsWithRef, ref) => ( diff --git a/packages/interface/src/components/ui/RadioSelect.tsx b/packages/interface/src/components/ui/RadioSelect.tsx new file mode 100644 index 00000000..1811263d --- /dev/null +++ b/packages/interface/src/components/ui/RadioSelect.tsx @@ -0,0 +1,59 @@ +import clsx from "clsx"; +import Image from "next/image"; +import React, { useCallback } from "react"; +import { useFormContext } from "react-hook-form"; + +import { Label } from "~/components/ui/Form"; +import { Input } from "~/components/ui/Input"; +import { Tooltip } from "~/components/ui/Tooltip"; + +interface IRadioSelectProps { + label: string; + name: string; + hint?: string; + required?: boolean; + options: string[]; +} + +export const RadioSelect = ({ label, name, required = false, hint = "", options }: IRadioSelectProps): JSX.Element => { + const form = useFormContext(); + + const handleOnClick = useCallback( + (e: React.ChangeEvent) => { + form.setValue(name, e.target.id); + }, + [form], + ); + + return ( +
+
+ {label && ( + + )} + + {hint && } +
+ +
+ {options.map((option) => ( + + ))} +
+
+ ); +}; diff --git a/packages/interface/src/contexts/Maci.tsx b/packages/interface/src/contexts/Maci.tsx index eee7116a..9d971c3e 100644 --- a/packages/interface/src/contexts/Maci.tsx +++ b/packages/interface/src/contexts/Maci.tsx @@ -209,12 +209,13 @@ export const MaciProvider: React.FC = ({ children }: MaciProv setSemaphoreIdentity(newSemaphoreIdentity); }, [address, signatureMessage, signMessageAsync, setMaciPrivKey, setMaciPubKey, setSemaphoreIdentity]); + /// TODO: the votingEndsAt should be got from the registry contract, the new Date() fallback is just a temporarily time // memo to calculate the voting end date const votingEndsAt = useMemo( () => pollData && pollData.duration !== 0 ? new Date(Number(pollData.deployTime) * 1000 + Number(pollData.duration) * 1000) - : config.resultsAt, + : new Date(), [pollData?.deployTime, pollData?.duration], ); @@ -403,7 +404,7 @@ export const MaciProvider: React.FC = ({ children }: MaciProv return data; }) .then(async (data) => { - if (!data.isMerged || (votingEndsAt && isAfter(votingEndsAt, new Date()))) { + if (!data.isMerged || isAfter(votingEndsAt, new Date())) { return undefined; } diff --git a/packages/interface/src/contexts/Round.tsx b/packages/interface/src/contexts/Round.tsx index a5415337..ad578714 100644 --- a/packages/interface/src/contexts/Round.tsx +++ b/packages/interface/src/contexts/Round.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useMemo, useCallback } from "react"; +import React, { createContext, useContext, useMemo, useCallback, useState, useEffect } from "react"; import type { RoundContextType, RoundProviderProps } from "./types"; import type { Round } from "~/features/rounds/types"; @@ -6,28 +6,56 @@ 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 [rounds, setRounds] = useState(undefined); + + const [isContractsDeployed, setContractsDeployed] = useState(false); const getRound = useCallback( - (roundId: string): Round | undefined => rounds.find((round) => round.roundId === roundId), + (roundId: string): Round | undefined => (rounds ? rounds.find((round) => round.roundId === roundId) : undefined), [rounds], ); + const addRound = useCallback( + (round: Round): void => { + if (!rounds) { + setRounds([round]); + } else { + setRounds([...rounds, round]); + } + }, + [rounds, setRounds], + ); + + const deployContracts = useCallback(() => { + setContractsDeployed(true); + }, [setContractsDeployed]); + + useEffect(() => { + const storageData = localStorage.getItem("rounds"); + + if (storageData) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const storedRounds = JSON.parse(storageData); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + setRounds(storedRounds); + } + }, []); + + useEffect(() => { + if (rounds) { + localStorage.setItem("rounds", JSON.stringify(rounds)); + } + }, [rounds]); + const value = useMemo( () => ({ + isContractsDeployed, rounds, getRound, + addRound, + deployContracts, }), - [rounds, getRound], + [rounds, getRound, addRound], ); return {children}; diff --git a/packages/interface/src/contexts/types.ts b/packages/interface/src/contexts/types.ts index 2637d0b8..2475594b 100644 --- a/packages/interface/src/contexts/types.ts +++ b/packages/interface/src/contexts/types.ts @@ -50,8 +50,11 @@ export interface BallotProviderProps { } export interface RoundContextType { - rounds: Round[]; + isContractsDeployed: boolean; + rounds: Round[] | undefined; getRound: (roundId: string) => Round | undefined; + addRound: (round: Round) => void; + deployContracts: () => void; } export interface RoundProviderProps { diff --git a/packages/interface/src/env.js b/packages/interface/src/env.js index 5ff03102..da42dd1d 100644 --- a/packages/interface/src/env.js +++ b/packages/interface/src/env.js @@ -44,7 +44,7 @@ module.exports = createEnv({ NEXT_PUBLIC_WALLETCONNECT_ID: z.string().optional(), NEXT_PUBLIC_ALCHEMY_ID: z.string().optional(), - NEXT_PUBLIC_MACI_ADDRESS: z.string().startsWith("0x"), + NEXT_PUBLIC_MACI_ADDRESS: z.string().startsWith("0x").optional(), NEXT_PUBLIC_MACI_START_BLOCK: z.string().optional(), NEXT_PUBLIC_MACI_SUBGRAPH_URL: z.string().url().optional(), diff --git a/packages/interface/src/features/admin/components/CheckItem.tsx b/packages/interface/src/features/admin/components/CheckItem.tsx new file mode 100644 index 00000000..9e376834 --- /dev/null +++ b/packages/interface/src/features/admin/components/CheckItem.tsx @@ -0,0 +1,18 @@ +import { FaCheckCircle } from "react-icons/fa"; + +import type { ReactNode } from "react"; + +interface ICheckItemProps { + text: string; + value: ReactNode; +} + +export const CheckItem = ({ text, value }: ICheckItemProps): JSX.Element => ( +
+ + +

{text}

+ +
{value}
+
+); diff --git a/packages/interface/src/features/admin/components/DeployContracts.tsx b/packages/interface/src/features/admin/components/DeployContracts.tsx new file mode 100644 index 00000000..ef2701ca --- /dev/null +++ b/packages/interface/src/features/admin/components/DeployContracts.tsx @@ -0,0 +1,83 @@ +import { useRouter } from "next/router"; +import { useState } from "react"; +import { useAccount } from "wagmi"; + +import { Steps } from "~/components/Steps"; +import { Form, FormSection } from "~/components/ui/Form"; +import { Heading } from "~/components/ui/Heading"; +import { RadioSelect } from "~/components/ui/RadioSelect"; +import { useRound } from "~/contexts/Round"; +import { DeploymentSchema, chainTypes, gatingStrategyTypes } from "~/features/rounds/types"; +import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork"; + +import { EDeployStep } from "../types"; + +import { DeployContractsButtons } from "./DeployContractsButtons"; +import { ReviewDeployContractsDetails } from "./ReviewDeployContractsDetails"; +import { VoiceCreditProxySelect } from "./VoiceCreditProxySelect"; + +export const DeployContracts = (): JSX.Element => { + const router = useRouter(); + const [step, setStep] = useState(EDeployStep.CONFIGURE); + + const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork(); + + const { address } = useAccount(); + + const { deployContracts } = useRound(); + + const onSubmit = () => { + deployContracts(); + router.push("/"); + }; + + return ( +
+ Deploy Core Contracts + +

These initial MACI core contracts configuration will apply to all future rounds.

+ +
+ + +
+ + + + + + + + + {step === EDeployStep.REVIEW && } + + {step === EDeployStep.REVIEW && ( +
+ {!address &&

You must connect wallet to create an application

} + + {!isCorrectNetwork &&

You must be connected to {correctNetwork.name}

} +
+ )} + + + +
+
+ ); +}; diff --git a/packages/interface/src/features/admin/components/DeployContractsButtons.tsx b/packages/interface/src/features/admin/components/DeployContractsButtons.tsx new file mode 100644 index 00000000..10f4a4b5 --- /dev/null +++ b/packages/interface/src/features/admin/components/DeployContractsButtons.tsx @@ -0,0 +1,60 @@ +import { useMemo } from "react"; +import { useFormContext } from "react-hook-form"; + +import { FormActionButtons } from "~/components/FormActionButtons"; +import { creditStrategyTypes, type Deployment } from "~/features/rounds/types"; + +import { EDeployStep } from "../types"; + +interface IDeployContractsButtonsProps { + step: EDeployStep; + isUploading: boolean; + isPending: boolean; + setStep: (step: EDeployStep) => void; +} + +export const DeployContractsButtons = ({ + step, + isUploading, + isPending, + setStep, +}: IDeployContractsButtonsProps): JSX.Element => { + const form = useFormContext(); + + const [creditStrategy, creditAmount] = useMemo(() => form.watch(["creditStrategy", "creditAmount"]), [form]); + + const checkStepComplete = (): boolean => { + if (step === EDeployStep.CONFIGURE && creditStrategy === creditStrategyTypes.CONSTANT) { + return creditAmount > 0; + } + + return true; + }; + + const onNextStep = () => { + if (!checkStepComplete()) { + throw new Error("Step not completed."); + } + + if (step === EDeployStep.CONFIGURE) { + setStep(EDeployStep.REVIEW); + } + }; + + const onBackStep = () => { + if (step === EDeployStep.REVIEW) { + setStep(EDeployStep.CONFIGURE); + } + }; + + return ( + + ); +}; diff --git a/packages/interface/src/features/admin/components/DeployRounds.tsx b/packages/interface/src/features/admin/components/DeployRounds.tsx new file mode 100644 index 00000000..6e7c3080 --- /dev/null +++ b/packages/interface/src/features/admin/components/DeployRounds.tsx @@ -0,0 +1,135 @@ +import { useRouter } from "next/router"; +import { useState } from "react"; +import { toast } from "sonner"; +import { useAccount } from "wagmi"; + +import { ImageUpload } from "~/components/ImageUpload"; +import { Steps } from "~/components/Steps"; +import { Form, FormSection, FormControl, Textarea, Select, DateInput } from "~/components/ui/Form"; +import { Heading } from "~/components/ui/Heading"; +import { Input } from "~/components/ui/Input"; +import { RoundSchema, votingStrategyTypes } from "~/features/rounds/types"; +import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork"; + +import { useDeployRound } from "../hooks/useDeployRound"; +import { EDeployStep } from "../types"; + +import { DeployRoundsButtons } from "./DeployRoundsButtons"; +import { ReviewDeployRoundDetails } from "./ReviewDeployRoundDetails"; + +export const DeployRounds = (): JSX.Element => { + const router = useRouter(); + + const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork(); + + const { address } = useAccount(); + + const [step, setStep] = useState(EDeployStep.CONFIGURE); + + const create = useDeployRound({ + onSuccess: () => { + router.push(`/`); + }, + onError: (err: Error) => + toast.error("Round deploy error", { + description: err.message, + }), + }); + + const { error: createError } = create; + + return ( +
+ Deploy Round Contracts + +

These round contracts specify the features for this round.

+ +
+ + +
{ + create.mutate(round); + }} + > + + + + + +
+ +