diff --git a/packages/interface/src/features/applications/components/ApplicationForm.tsx b/packages/interface/src/features/applications/components/ApplicationForm.tsx index 5d1924e7..12483e40 100644 --- a/packages/interface/src/features/applications/components/ApplicationForm.tsx +++ b/packages/interface/src/features/applications/components/ApplicationForm.tsx @@ -1,6 +1,5 @@ import { useRouter } from "next/router"; import { useState, useCallback } from "react"; -import { useLocalStorage } from "react-use"; import { toast } from "sonner"; import { useAccount } from "wagmi"; @@ -22,8 +21,6 @@ interface IApplicationFormProps { } export const ApplicationForm = ({ pollId }: IApplicationFormProps): JSX.Element => { - const clearDraft = useLocalStorage("application-draft")[2]; - const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork(); const { address } = useAccount(); @@ -56,7 +53,6 @@ export const ApplicationForm = ({ pollId }: IApplicationFormProps): JSX.Element const create = useCreateApplication({ onSuccess: (id: bigint) => { - clearDraft(); router.push(`/rounds/${pollId}/applications/confirmation?id=${id.toString()}`); }, onError: (err: { message: string }) => diff --git a/packages/interface/src/features/applications/components/ReviewApplicationDetails.tsx b/packages/interface/src/features/applications/components/ReviewApplicationDetails.tsx index d7977eaa..877e6f14 100644 --- a/packages/interface/src/features/applications/components/ReviewApplicationDetails.tsx +++ b/packages/interface/src/features/applications/components/ReviewApplicationDetails.tsx @@ -1,6 +1,7 @@ import clsx from "clsx"; import { useMemo, type ReactNode } from "react"; import { useFormContext } from "react-hook-form"; +import { useAccount } from "wagmi"; import { Heading } from "~/components/ui/Heading"; import { Tag } from "~/components/ui/Tag"; @@ -41,6 +42,8 @@ export const ReviewApplicationDetails = (): JSX.Element => { const application = useMemo(() => form.getValues(), [form]); + const { address } = useAccount(); + return (
@@ -54,6 +57,8 @@ export const ReviewApplicationDetails = (): JSX.Element => { + +
diff --git a/packages/interface/src/features/applications/hooks/useApplicationById.ts b/packages/interface/src/features/applications/hooks/useApplicationById.ts deleted file mode 100644 index 2cf017c6..00000000 --- a/packages/interface/src/features/applications/hooks/useApplicationById.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { api } from "~/utils/api"; - -import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { IRequest } from "~/utils/types"; - -export function useApplicationById(registryAddress: string, id: string): UseTRPCQueryResult { - return api.applications.getById.useQuery({ registryAddress, id }); -} diff --git a/packages/interface/src/features/applications/hooks/useApplications.ts b/packages/interface/src/features/applications/hooks/useApplications.ts index 0e118bdd..ab03339c 100644 --- a/packages/interface/src/features/applications/hooks/useApplications.ts +++ b/packages/interface/src/features/applications/hooks/useApplications.ts @@ -1,7 +1,7 @@ import { api } from "~/utils/api"; import type { UseTRPCQueryResult } from "@trpc/react-query/shared"; -import type { IRequest } from "~/utils/types"; +import type { IRequest, IRecipient } from "~/utils/types"; export function useApprovedApplications(registryAddress: string): UseTRPCQueryResult { return api.applications.approvals.useQuery({ registryAddress }); @@ -17,3 +17,19 @@ export function useApplicationByProjectId( ): UseTRPCQueryResult { return api.applications.getByProjectId.useQuery({ projectId, registryAddress }); } + +export function useAllApplications(registryAddress: string): UseTRPCQueryResult { + return api.applications.getByIds.useQuery({ registryAddress, ids: [] }); +} + +export function useApplicationById(registryAddress: string, id: string): UseTRPCQueryResult { + return api.applications.getById.useQuery({ registryAddress, id }); +} + +export function useApplicationsByIds(registryAddress: string, ids: string[]): UseTRPCQueryResult { + return api.applications.getByIds.useQuery({ registryAddress, ids }); +} + +export function useMyApplications(registryAddress: string, address: string): UseTRPCQueryResult { + return api.projects.getMine.useQuery({ registryAddress, address }); +} diff --git a/packages/interface/src/features/applications/hooks/useCreateApplication.ts b/packages/interface/src/features/applications/hooks/useCreateApplication.ts index f9e51e67..df2e6c61 100644 --- a/packages/interface/src/features/applications/hooks/useCreateApplication.ts +++ b/packages/interface/src/features/applications/hooks/useCreateApplication.ts @@ -34,7 +34,7 @@ export function useCreateApplication(options: { }): TUseCreateApplicationReturn { const upload = useUploadMetadata(); - const { chain } = useAccount(); + const { chain, address } = useAccount(); const { getRoundByPollId } = useRound(); const roundData = getRoundByPollId(options.pollId); @@ -44,7 +44,7 @@ export function useCreateApplication(options: { const mutation = useMutation({ mutationFn: async (values: Application) => { - if (!signer || !chain) { + if (!signer || !chain || !address) { throw new Error("Please connect your wallet first"); } @@ -71,6 +71,7 @@ export function useCreateApplication(options: { profileImageUrl: profileImageUrl.url, bannerImageUrl: bannerImageUrl.url, submittedAt: Date.now().valueOf(), + creator: address, }; const uploadRes = await upload.mutateAsync(metadataValues); diff --git a/packages/interface/src/features/applications/types/index.ts b/packages/interface/src/features/applications/types/index.ts index 683369be..138153c6 100644 --- a/packages/interface/src/features/applications/types/index.ts +++ b/packages/interface/src/features/applications/types/index.ts @@ -25,6 +25,7 @@ export const fundingSourceTypes = { export const ApplicationSchema = z.object({ name: z.string().min(3), bio: z.string().min(3), + creator: z.string().optional(), profileImageUrl: z.string().optional(), bannerImageUrl: z.string().optional(), submittedAt: z.number().optional(), diff --git a/packages/interface/src/features/projects/components/ProjectItem.tsx b/packages/interface/src/features/projects/components/ProjectItem.tsx index db4142ca..d78b42fb 100644 --- a/packages/interface/src/features/projects/components/ProjectItem.tsx +++ b/packages/interface/src/features/projects/components/ProjectItem.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; +import Link from "next/link"; import { Button } from "~/components/ui/Button"; import { Heading } from "~/components/ui/Heading"; @@ -36,57 +37,59 @@ export const ProjectItem = ({ const roundState = useRoundState({ pollId }); return ( -
-
- - - -
- -
- - {metadata.data?.name} - - -
- - {metadata.data?.bio} - + +
+
+ + +
- - - - - {!isLoading && state !== undefined && action && roundState === ERoundState.VOTING && ( -
- - {state === EProjectState.DEFAULT && ( - - )} - - {state === EProjectState.ADDED && ( - - )} - - {state === EProjectState.SUBMITTED && ( - - )} +
+ + {metadata.data?.name} + + +
+ + {metadata.data?.bio}
- )} -
-
+ + + + + + {!isLoading && state !== undefined && action && roundState === ERoundState.VOTING && ( +
+ + {state === EProjectState.DEFAULT && ( + + )} + + {state === EProjectState.ADDED && ( + + )} + + {state === EProjectState.SUBMITTED && ( + + )} + +
+ )} +
+
+ ); }; diff --git a/packages/interface/src/features/projects/components/ProjectsResults.tsx b/packages/interface/src/features/projects/components/ProjectsResults.tsx index 5b112b4d..4ef3c166 100644 --- a/packages/interface/src/features/projects/components/ProjectsResults.tsx +++ b/packages/interface/src/features/projects/components/ProjectsResults.tsx @@ -1,5 +1,4 @@ import clsx from "clsx"; -import Link from "next/link"; import { useRouter } from "next/router"; import { useCallback, useMemo } from "react"; import { type Hex, zeroAddress } from "viem"; @@ -43,11 +42,7 @@ export const ProjectsResults = ({ pollId }: IProjectsResultsProps): JSX.Element ( - +
{!results.isLoading && roundState === ERoundState.RESULTS ? ( ) : null} @@ -59,7 +54,7 @@ export const ProjectsResults = ({ pollId }: IProjectsResultsProps): JSX.Element recipient={item} state={EProjectState.SUBMITTED} /> - +
)} /> ); diff --git a/packages/interface/src/features/rounds/components/Projects.tsx b/packages/interface/src/features/rounds/components/Projects.tsx index 606e902f..0a06886b 100644 --- a/packages/interface/src/features/rounds/components/Projects.tsx +++ b/packages/interface/src/features/rounds/components/Projects.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { useCallback, useMemo } from "react"; import { FiAlertCircle } from "react-icons/fi"; import { Hex, zeroAddress } from "viem"; +import { useAccount } from "wagmi"; import { InfiniteLoading } from "~/components/InfiniteLoading"; import { SortFilter } from "~/components/SortFilter"; @@ -12,6 +13,7 @@ import { Heading } from "~/components/ui/Heading"; import { useBallot } from "~/contexts/Ballot"; import { useMaci } from "~/contexts/Maci"; import { useRound } from "~/contexts/Round"; +import { useMyApplications } from "~/features/applications/hooks/useApplications"; import { useResults } from "~/hooks/useResults"; import { useRoundState } from "~/utils/state"; import { ERoundState } from "~/utils/types"; @@ -27,6 +29,8 @@ export interface IProjectsProps { export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => { const roundState = useRoundState({ pollId }); + const { address } = useAccount(); + const { getRoundByPollId } = useRound(); const round = useMemo(() => getRoundByPollId(pollId), [pollId, getRoundByPollId]); @@ -43,6 +47,13 @@ export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => { const ballot = useMemo(() => getBallot(pollId), [pollId, getBallot]); + /** + * Find my applications: "I" am either the "creator" or the "payout address" + */ + const applications = useMyApplications(round?.registryAddress ?? zeroAddress, address ?? zeroAddress); + + const myApplications = useMemo(() => applications.data, [applications]); + const handleAction = useCallback( (projectIndex: number, projectId: string) => (e: Event) => { e.preventDefault(); @@ -118,24 +129,36 @@ export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => {
- {roundState === ERoundState.APPLICATION && ( -
- - - + {roundState === ERoundState.APPLICATION && address && ( +
+
+ My Projects + + + + +
+ +
+ {myApplications && + myApplications.length > 0 && + myApplications.map((project) => ( + + ))} + + {(!myApplications || myApplications.length === 0) && ( +

Create your application by clicking the button

+ )} +
)} ( - +
{!results.isLoading && roundState === ERoundState.RESULTS ? ( ) : null} @@ -147,7 +170,7 @@ export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => { recipient={item} state={defineState(Number.parseInt(item.index, 10))} /> - +
)} />
diff --git a/packages/interface/src/hooks/useRegistry.ts b/packages/interface/src/hooks/useRegistry.ts index 7d2efdbb..b9151e08 100644 --- a/packages/interface/src/hooks/useRegistry.ts +++ b/packages/interface/src/hooks/useRegistry.ts @@ -22,7 +22,7 @@ interface SubmitApplicationArgs { */ registryAddress: Hex; /** - * The recipient of the attestation + * The recipient of the application */ recipient: Hex; } diff --git a/packages/interface/src/pages/rounds/[pollId]/applications/confirmation.tsx b/packages/interface/src/pages/rounds/[pollId]/applications/confirmation.tsx index 49b932a7..6bacfc79 100644 --- a/packages/interface/src/pages/rounds/[pollId]/applications/confirmation.tsx +++ b/packages/interface/src/pages/rounds/[pollId]/applications/confirmation.tsx @@ -1,4 +1,3 @@ -import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { useMemo } from "react"; import { FiAlertCircle } from "react-icons/fi"; @@ -8,7 +7,7 @@ import { EmptyState } from "~/components/EmptyState"; import { Alert } from "~/components/ui/Alert"; import { Heading } from "~/components/ui/Heading"; import { useRound } from "~/contexts/Round"; -import { useApplicationById } from "~/features/applications/hooks/useApplicationById"; +import { useApplicationById } from "~/features/applications/hooks/useApplications"; import { ProjectItem } from "~/features/projects/components/ProjectItem"; import { Layout } from "~/layouts/DefaultLayout"; import { useRoundState } from "~/utils/state"; @@ -83,9 +82,7 @@ const ConfirmProjectPage = ({ pollId }: { pollId: string }): JSX.Element => {
)} - - - + diff --git a/packages/interface/src/server/api/routers/applications.ts b/packages/interface/src/server/api/routers/applications.ts index dfe3d57c..14f9e657 100644 --- a/packages/interface/src/server/api/routers/applications.ts +++ b/packages/interface/src/server/api/routers/applications.ts @@ -6,18 +6,31 @@ import { fetchApplicationByProjectId, fetchApprovedApplications, fetchPendingApplications, + fetchApplications, } from "~/utils/fetchApplications"; export const applicationsRouter = createTRPCRouter({ approvals: publicProcedure .input(z.object({ registryAddress: z.string() })) .query(async ({ input }) => fetchApprovedApplications(input.registryAddress)), + pending: publicProcedure .input(z.object({ registryAddress: z.string() })) .query(async ({ input }) => fetchPendingApplications(input.registryAddress)), + getById: publicProcedure .input(z.object({ registryAddress: z.string(), id: z.string() })) .query(async ({ input }) => fetchApplicationById(input.registryAddress, input.id)), + + getByIds: publicProcedure + .input(z.object({ registryAddress: z.string(), ids: z.array(z.string()) })) + .query(async ({ input }) => { + if (input.ids.length > 0) { + return Promise.all(input.ids.map((id) => fetchApplicationById(input.registryAddress, id))); + } + return fetchApplications(input.registryAddress); + }), + getByProjectId: publicProcedure .input(z.object({ registryAddress: z.string(), projectId: z.string() })) .query(async ({ input }) => fetchApplicationByProjectId(input.projectId, input.registryAddress)), diff --git a/packages/interface/src/server/api/routers/projects.ts b/packages/interface/src/server/api/routers/projects.ts index a1dfad3f..cf33ee66 100644 --- a/packages/interface/src/server/api/routers/projects.ts +++ b/packages/interface/src/server/api/routers/projects.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { FilterSchema } from "~/features/filter/types"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { fetchApprovedProjects, fetchProjects } from "~/utils/fetchProjects"; +import { fetchApprovedProjects, fetchProjects, fetchProjectsByAddress } from "~/utils/fetchProjects"; import { getProjectCount } from "~/utils/registry"; import type { Chain, Hex } from "viem"; @@ -39,6 +39,10 @@ export const projectsRouter = createTRPCRouter({ .input(FilterSchema.extend({ registryAddress: z.string() })) .query(async ({ input }) => fetchProjects(input.registryAddress)), + getMine: publicProcedure + .input(z.object({ registryAddress: z.string(), address: z.string() })) + .query(async ({ input }) => fetchProjectsByAddress(input.registryAddress, input.address)), + // Used for distribution to get the projects' payoutAddress // To get this data we need to fetch all projects and their metadata // payoutAddresses: publicProcedure.input(z.object({ ids: z.array(z.string()) })).query(async ({ input }) => diff --git a/packages/interface/src/utils/fetchApplications.ts b/packages/interface/src/utils/fetchApplications.ts index 63280740..1ea284a8 100644 --- a/packages/interface/src/utils/fetchApplications.ts +++ b/packages/interface/src/utils/fetchApplications.ts @@ -65,6 +65,25 @@ const ApprovedRequests = ` } `; +// Fetch all add requests +const AllRequests = ` + query AllRequests($registryAddress: String!) { + requests(where: { requestType: "Add", recipient_: { registry: $registryAddress } }) { + index + recipient { + id + metadataUrl + index + initialized + payout + registry { + id + } + } + } + } +`; + const IndividualRequest = ` query PendingAddRequests($registryAddress: String!, $recipientId: String!) { requests(where: { requestType: "Add", status: "Pending", recipient_: { @@ -226,7 +245,7 @@ export async function fetchApplications(registryAddress: string): Promise { + const response = await fetch(config.maciSubgraphUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: ProjectsByAddress, + variables: { registryAddress, address }, + }), + }); + + const result = (await response.json()) as GraphQLResponse; + + if (!result.data) { + throw new Error("No data returned from GraphQL query"); + } + + return result.data.recipients; +}