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 983ffc53..90886680 100644
--- a/packages/interface/src/components/Info.tsx
+++ b/packages/interface/src/components/Info.tsx
@@ -65,7 +65,7 @@ export const Info = ({ size, roundId, showVotingInfo = false }: IInfoProps): JSX
{showVotingInfo && (
-
+
{roundState === ERoundState.VOTING && }
diff --git a/packages/interface/src/components/RoundInfo.tsx b/packages/interface/src/components/RoundInfo.tsx
index 6281c17b..5448fbf2 100644
--- a/packages/interface/src/components/RoundInfo.tsx
+++ b/packages/interface/src/components/RoundInfo.tsx
@@ -1,18 +1,16 @@
-import Image from "next/image";
-
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 &&
}
+ {roundLogo &&
}
{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 && (
@@ -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..49a60899
--- /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/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/DeployContracts.tsx b/packages/interface/src/features/admin/components/DeployContracts.tsx
new file mode 100644
index 00000000..d6238fb3
--- /dev/null
+++ b/packages/interface/src/features/admin/components/DeployContracts.tsx
@@ -0,0 +1,99 @@
+import { useRouter } from "next/router";
+import { useState, useCallback } 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 { EDeployContractsStep, DeployContractsButtons } from "./DeployContractsButtons";
+import { ReviewDeployContractsDetails } from "./ReviewDeployContractsDetails";
+import { VoiceCreditProxySelect } from "./VoiceCreditProxySelect";
+
+export const DeployContracts = (): JSX.Element => {
+ const router = useRouter();
+ const [step, setStep] = useState(EDeployContractsStep.CONFIGURE);
+
+ const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork();
+
+ const { address } = useAccount();
+
+ const { deployContracts } = useRound();
+
+ const handleNextStep = useCallback(() => {
+ if (step === EDeployContractsStep.CONFIGURE) {
+ setStep(EDeployContractsStep.REVIEW);
+ }
+ }, [step, setStep]);
+
+ const handleBackStep = useCallback(() => {
+ if (step === EDeployContractsStep.REVIEW) {
+ setStep(EDeployContractsStep.CONFIGURE);
+ }
+ }, [step, setStep]);
+
+ const onSubmit = () => {
+ deployContracts();
+ router.push("/");
+ };
+
+ return (
+
+
Deploy Core Contracts
+
+
These initial MACI core contracts configuration will apply to all future rounds.
+
+
+
+ );
+};
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..dd367987
--- /dev/null
+++ b/packages/interface/src/features/admin/components/DeployContractsButtons.tsx
@@ -0,0 +1,113 @@
+import { useMemo, useCallback, useState } from "react";
+import { useFormContext } from "react-hook-form";
+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";
+
+import type { Deployment } from "~/features/rounds/types";
+
+export enum EDeployContractsStep {
+ CONFIGURE,
+ REVIEW,
+}
+
+interface IDeployContractsButtonsProps {
+ step: EDeployContractsStep;
+ isUploading: boolean;
+ isPending: boolean;
+ onNextStep: () => void;
+ onBackStep: () => void;
+}
+
+export const DeployContractsButtons = ({
+ step,
+ isUploading,
+ isPending,
+ onNextStep,
+ onBackStep,
+}: IDeployContractsButtonsProps): JSX.Element => {
+ const { isCorrectNetwork } = useIsCorrectNetwork();
+
+ const { address } = useAccount();
+
+ const [showDialog, setShowDialog] = useState(false);
+ const form = useFormContext();
+
+ const [chain, gatingStrategy, creditStrategy] = useMemo(
+ () => form.watch(["chain", "gatingStrategy", "creditStrategy"]),
+ [form],
+ );
+
+ const stepComplete = useMemo((): boolean => {
+ if (step === EDeployContractsStep.CONFIGURE) {
+ return true;
+ }
+
+ return true;
+ }, [chain, gatingStrategy, creditStrategy]);
+
+ const handleOnClickNextStep = useCallback(
+ (event: UIEvent) => {
+ event.preventDefault();
+
+ if (stepComplete) {
+ onNextStep();
+ } else {
+ setShowDialog(true);
+ }
+ },
+ [onNextStep, setShowDialog, stepComplete],
+ );
+
+ const handleOnClickBackStep = useCallback(
+ (event: UIEvent) => {
+ event.preventDefault();
+ onBackStep();
+ },
+ [onBackStep],
+ );
+
+ const handleOnOpenChange = useCallback(() => {
+ setShowDialog(false);
+ }, [setShowDialog]);
+
+ return (
+
+
+
+ {step !== EDeployContractsStep.CONFIGURE && (
+
+ )}
+
+ {step !== EDeployContractsStep.REVIEW && (
+
+ )}
+
+ {step === EDeployContractsStep.REVIEW && (
+
+ {isUploading ? "Uploading..." : "Submit"}
+
+ )}
+
+ );
+};
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..db795ef0
--- /dev/null
+++ b/packages/interface/src/features/admin/components/DeployRounds.tsx
@@ -0,0 +1,147 @@
+import { useRouter } from "next/router";
+import { useState, useCallback } 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 { DeployRoundsButtons, EDeployRoundsStep } 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(EDeployRoundsStep.CONFIGURE);
+
+ const handleNextStep = useCallback(() => {
+ if (step === EDeployRoundsStep.CONFIGURE) {
+ setStep(EDeployRoundsStep.REVIEW);
+ }
+ }, [step, setStep]);
+
+ const handleBackStep = useCallback(() => {
+ if (step === EDeployRoundsStep.REVIEW) {
+ setStep(EDeployRoundsStep.CONFIGURE);
+ }
+ }, [step, setStep]);
+
+ 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.
+
+
+
+ );
+};
diff --git a/packages/interface/src/features/admin/components/DeployRoundsButtons.tsx b/packages/interface/src/features/admin/components/DeployRoundsButtons.tsx
new file mode 100644
index 00000000..18ed6944
--- /dev/null
+++ b/packages/interface/src/features/admin/components/DeployRoundsButtons.tsx
@@ -0,0 +1,131 @@
+import { useMemo, useCallback, useState } from "react";
+import { useFormContext } from "react-hook-form";
+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";
+
+import type { Round } from "~/features/rounds/types";
+
+export enum EDeployRoundsStep {
+ CONFIGURE,
+ REVIEW,
+}
+
+interface IDeployRoundsButtonsProps {
+ step: EDeployRoundsStep;
+ isUploading: boolean;
+ isPending: boolean;
+ onNextStep: () => void;
+ onBackStep: () => void;
+}
+
+export const DeployRoundsButtons = ({
+ step,
+ isUploading,
+ isPending,
+ onNextStep,
+ onBackStep,
+}: IDeployRoundsButtonsProps): JSX.Element => {
+ const { isCorrectNetwork } = useIsCorrectNetwork();
+
+ const { address } = useAccount();
+
+ const [showDialog, setShowDialog] = useState(false);
+
+ const form = useFormContext();
+
+ const [roundId, description, roundLogo, startsAt, registrationEndsAt, votingStartsAt, votingEndsAt] = useMemo(
+ () =>
+ form.watch([
+ "roundId",
+ "description",
+ "roundLogo",
+ "startsAt",
+ "registrationEndsAt",
+ "votingStartsAt",
+ "votingEndsAt",
+ ]),
+ [form],
+ );
+
+ const stepComplete = useMemo((): boolean => {
+ if (step === EDeployRoundsStep.CONFIGURE) {
+ return (
+ roundId.length > 0 &&
+ description.length > 0 &&
+ roundLogo !== undefined &&
+ startsAt.length > 0 &&
+ registrationEndsAt.length > 0 &&
+ votingStartsAt.length > 0 &&
+ votingEndsAt.length > 0
+ );
+ }
+
+ return true;
+ }, [roundId, description, roundLogo, startsAt, registrationEndsAt, votingStartsAt, votingEndsAt]);
+
+ const handleOnClickNextStep = useCallback(
+ (event: UIEvent) => {
+ event.preventDefault();
+
+ if (stepComplete) {
+ onNextStep();
+ } else {
+ setShowDialog(true);
+ }
+ },
+ [onNextStep, setShowDialog, stepComplete],
+ );
+
+ const handleOnClickBackStep = useCallback(
+ (event: UIEvent) => {
+ event.preventDefault();
+ onBackStep();
+ },
+ [onBackStep],
+ );
+
+ const handleOnOpenChange = useCallback(() => {
+ setShowDialog(false);
+ }, [setShowDialog]);
+
+ return (
+
+
+
+ {step !== EDeployRoundsStep.CONFIGURE && (
+
+ )}
+
+ {step !== EDeployRoundsStep.REVIEW && (
+
+ )}
+
+ {step === EDeployRoundsStep.REVIEW && (
+
+ {isUploading ? "Uploading..." : "Submit"}
+
+ )}
+
+ );
+};
diff --git a/packages/interface/src/features/admin/components/ReviewDeployContractsDetails.tsx b/packages/interface/src/features/admin/components/ReviewDeployContractsDetails.tsx
new file mode 100644
index 00000000..b678ece4
--- /dev/null
+++ b/packages/interface/src/features/admin/components/ReviewDeployContractsDetails.tsx
@@ -0,0 +1,69 @@
+import { useMemo, type ReactNode } from "react";
+import { useFormContext } from "react-hook-form";
+import { FaCheckCircle } from "react-icons/fa";
+
+import { Heading } from "~/components/ui/Heading";
+
+import type { Deployment } from "~/features/rounds/types";
+
+interface ICheckItemProps {
+ text: string;
+ value: ReactNode;
+}
+
+const CheckItem = ({ text, value }: ICheckItemProps): JSX.Element => (
+
+
+
+
{text}
+
+
{value}
+
+);
+
+export const ReviewDeployContractsDetails = (): JSX.Element => {
+ const form = useFormContext();
+
+ const deployment = useMemo(() => form.getValues(), [form]);
+
+ return (
+
+
Review and Deploy
+
+
Please review and deploy your host contracts.
+
+
+ }
+ />
+
+
+ {deployment.gatingStrategy}
+
+ to gate access to all upcoming rounds.
+
+ }
+ />
+
+
+ {`constant ${deployment.creditAmount} voice credits`}
+
+ allocation for all upcoming rounds.
+
+ }
+ />
+
+
+ );
+};
diff --git a/packages/interface/src/features/admin/components/ReviewDeployRoundDetails.tsx b/packages/interface/src/features/admin/components/ReviewDeployRoundDetails.tsx
new file mode 100644
index 00000000..b7eee001
--- /dev/null
+++ b/packages/interface/src/features/admin/components/ReviewDeployRoundDetails.tsx
@@ -0,0 +1,69 @@
+import { useMemo, type ReactNode } from "react";
+import { useFormContext } from "react-hook-form";
+import { FaCheckCircle } from "react-icons/fa";
+
+import { Heading } from "~/components/ui/Heading";
+import { formatPeriod } from "~/utils/time";
+
+import type { Round } from "~/features/rounds/types";
+
+interface ICheckItemProps {
+ text: string;
+ value: ReactNode;
+}
+
+const CheckItem = ({ text, value }: ICheckItemProps): JSX.Element => (
+
+
+
+
{text}
+
+
{value}
+
+);
+
+export const ReviewDeployRoundDetails = (): JSX.Element => {
+ const form = useFormContext
();
+
+ const round = useMemo(() => form.getValues(), [form]);
+
+ return (
+
+
Review and Deploy
+
+
Please review and deploy your round contract.
+
+
+
+
+
+ {round.roundId}
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/interface/src/features/admin/components/VoiceCreditProxySelect.tsx b/packages/interface/src/features/admin/components/VoiceCreditProxySelect.tsx
new file mode 100644
index 00000000..8c552dc9
--- /dev/null
+++ b/packages/interface/src/features/admin/components/VoiceCreditProxySelect.tsx
@@ -0,0 +1,53 @@
+import React, { useMemo, useCallback } from "react";
+import { useFormContext } from "react-hook-form";
+import { NumericFormat } from "react-number-format";
+
+import { Label } from "~/components/ui/Form";
+import { Input } from "~/components/ui/Input";
+import { RadioSelect } from "~/components/ui/RadioSelect";
+import { creditStrategyTypes, type Deployment } from "~/features/rounds/types";
+
+export const VoiceCreditProxySelect = (): JSX.Element => {
+ const form = useFormContext
();
+
+ const [creditStrategy] = useMemo(() => form.watch(["creditStrategy"]), [form]);
+
+ const handleInput = useCallback(
+ (e: React.ChangeEvent) => {
+ form.setValue("creditAmount", Number(e.target.value));
+ },
+ [form],
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/interface/src/features/admin/hooks/useDeployRound.ts b/packages/interface/src/features/admin/hooks/useDeployRound.ts
new file mode 100644
index 00000000..e414d112
--- /dev/null
+++ b/packages/interface/src/features/admin/hooks/useDeployRound.ts
@@ -0,0 +1,44 @@
+import { type UseMutationResult, useMutation } from "@tanstack/react-query";
+
+import { useRound } from "~/contexts/Round";
+import { useUploadMetadata } from "~/hooks/useMetadata";
+
+import type { Round } from "~/features/rounds/types";
+import type { TransactionError } from "~/features/voters/hooks/useApproveVoters";
+
+export type TUseDeployRoundReturn = Omit, "error"> & {
+ error: Error | TransactionError | null;
+ isDeploying: boolean;
+};
+
+export function useDeployRound(options: {
+ onSuccess: (data: string) => void;
+ onError: (err: Error) => void;
+}): TUseDeployRoundReturn {
+ const upload = useUploadMetadata();
+
+ const { addRound } = useRound();
+
+ const mutation = useMutation({
+ mutationFn: async (values: Round) => {
+ if (!values.roundLogo) {
+ throw new Error("No images included.");
+ }
+
+ const roundLogoImageFile = await fetch(values.roundLogo).then((logoImage) => logoImage.blob());
+
+ const roundLogoUrl = await upload.mutateAsync(new File([roundLogoImageFile], "roundLogo"));
+
+ addRound({ ...values, roundLogo: roundLogoUrl.url });
+
+ return roundLogoUrl.url;
+ },
+ ...options,
+ });
+
+ return {
+ ...mutation,
+ error: upload.error ?? mutation.error,
+ isDeploying: upload.isPending,
+ };
+}
diff --git a/packages/interface/src/features/admin/types/index.ts b/packages/interface/src/features/admin/types/index.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/interface/src/features/applications/components/ApplicationForm.tsx b/packages/interface/src/features/applications/components/ApplicationForm.tsx
index a17e20e7..fa049b94 100644
--- a/packages/interface/src/features/applications/components/ApplicationForm.tsx
+++ b/packages/interface/src/features/applications/components/ApplicationForm.tsx
@@ -6,6 +6,7 @@ import { toast } from "sonner";
import { useAccount } from "wagmi";
import { ImageUpload } from "~/components/ImageUpload";
+import { Steps } from "~/components/Steps";
import { FieldArray, Form, FormControl, FormSection, Select, Textarea } from "~/components/ui/Form";
import { Input } from "~/components/ui/Input";
import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork";
@@ -14,7 +15,6 @@ import { useCreateApplication } from "../hooks/useCreateApplication";
import { ApplicationSchema, contributionTypes, fundingSourceTypes } from "../types";
import { ApplicationButtons, EApplicationStep } from "./ApplicationButtons";
-import { ApplicationSteps } from "./ApplicationSteps";
import { ImpactTags } from "./ImpactTags";
import { ReviewApplicationDetails } from "./ReviewApplicationDetails";
@@ -71,7 +71,7 @@ export const ApplicationForm = ({ roundId }: IApplicationFormProps): JSX.Element
return (
-
+
)}
- {isConnected && !isAdmin && rounds.length === 0 && (
- There are no rounds deployed.
- )}
+ {isConnected && !isAdmin && !rounds && There are no rounds deployed.
}
- {rounds.length > 0 && }
+ {rounds && rounds.length > 0 && }