Skip to content

Commit

Permalink
feat: display the projects created by the user
Browse files Browse the repository at this point in the history
  • Loading branch information
kittybest committed Jan 10, 2025
1 parent 81179a4 commit aca66f6
Show file tree
Hide file tree
Showing 15 changed files with 191 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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();
Expand Down Expand Up @@ -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 }) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -41,6 +42,8 @@ export const ReviewApplicationDetails = (): JSX.Element => {

const application = useMemo(() => form.getValues(), [form]);

const { address } = useAccount();

return (
<div className="flex flex-col gap-8">
<div>
Expand All @@ -54,6 +57,8 @@ export const ReviewApplicationDetails = (): JSX.Element => {

<ValueField required body={application.name} title="Project name" />

<ValueField required body={address} title="Created By" />

<ValueField required body={application.bio} title="Project description" />

<div className="grid grid-flow-row gap-4 sm:grid-cols-2">
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<IRequest[], unknown> {
return api.applications.approvals.useQuery({ registryAddress });
Expand All @@ -17,3 +17,19 @@ export function useApplicationByProjectId(
): UseTRPCQueryResult<IRequest, unknown> {
return api.applications.getByProjectId.useQuery({ projectId, registryAddress });
}

export function useAllApplications(registryAddress: string): UseTRPCQueryResult<IRequest, unknown> {
return api.applications.getByIds.useQuery({ registryAddress, ids: [] });
}

export function useApplicationById(registryAddress: string, id: string): UseTRPCQueryResult<IRequest, unknown> {
return api.applications.getById.useQuery({ registryAddress, id });
}

export function useApplicationsByIds(registryAddress: string, ids: string[]): UseTRPCQueryResult<IRequest[], unknown> {
return api.applications.getByIds.useQuery({ registryAddress, ids });
}

export function useMyApplications(registryAddress: string, address: string): UseTRPCQueryResult<IRecipient[], unknown> {
return api.projects.getMine.useQuery({ registryAddress, address });
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");
}

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
97 changes: 50 additions & 47 deletions packages/interface/src/features/projects/components/ProjectItem.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -36,57 +37,59 @@ export const ProjectItem = ({
const roundState = useRoundState({ pollId });

return (
<article
className="dark:bg-lightBlack group w-96 rounded-xl bg-white shadow-lg hover:shadow-sm sm:w-full"
data-testid={`project-${recipient.id}`}
>
<div className="opacity-70 transition-opacity group-hover:opacity-100">
<ProjectBanner url={metadata.data?.bannerImageUrl} />

<ProjectAvatar className="-mt-8 ml-4" rounded="full" url={metadata.data?.profileImageUrl} />
</div>

<div className="p-4 pt-2">
<Heading as="h3" className="truncate dark:text-white" size="lg">
<Skeleton isLoading={isLoading}>{metadata.data?.name}</Skeleton>
</Heading>

<div className="line-clamp-2 h-10 text-sm text-gray-400">
<Skeleton className="w-full" isLoading={isLoading}>
{metadata.data?.bio}
</Skeleton>
<Link href={`/rounds/${pollId}/${recipient.id}`}>
<article
className="dark:bg-lightBlack group rounded-xl bg-white shadow-lg hover:shadow-sm sm:w-full"
data-testid={`project-${recipient.id}`}
>
<div className="opacity-70 transition-opacity group-hover:opacity-100">
<ProjectBanner url={metadata.data?.bannerImageUrl} />

<ProjectAvatar className="-mt-8 ml-4" rounded="full" url={metadata.data?.profileImageUrl} />
</div>

<Skeleton className="w-[100px]" isLoading={isLoading}>
<ImpactCategories tags={metadata.data?.impactCategory} />
</Skeleton>

{!isLoading && state !== undefined && action && roundState === ERoundState.VOTING && (
<div className="flex justify-end pt-6">
<Skeleton>
{state === EProjectState.DEFAULT && (
<Button size="sm" variant="inverted" onClick={action}>
Add to ballot
</Button>
)}

{state === EProjectState.ADDED && (
<Button size="sm" variant="primary" onClick={action}>
Added
<Image alt="check-white" height="18" src="/check-white.svg" width="18" />
</Button>
)}

{state === EProjectState.SUBMITTED && (
<Button size="sm" variant="disabled">
Submitted
</Button>
)}
<div className="p-4 pt-2">
<Heading as="h3" className="truncate dark:text-white" size="lg">
<Skeleton isLoading={isLoading}>{metadata.data?.name}</Skeleton>
</Heading>

<div className="line-clamp-2 h-10 text-sm text-gray-400">
<Skeleton className="w-full" isLoading={isLoading}>
{metadata.data?.bio}
</Skeleton>
</div>
)}
</div>
</article>

<Skeleton className="w-[100px]" isLoading={isLoading}>
<ImpactCategories tags={metadata.data?.impactCategory} />
</Skeleton>

{!isLoading && state !== undefined && action && roundState === ERoundState.VOTING && (
<div className="flex justify-end pt-6">
<Skeleton>
{state === EProjectState.DEFAULT && (
<Button size="sm" variant="inverted" onClick={action}>
Add to ballot
</Button>
)}

{state === EProjectState.ADDED && (
<Button size="sm" variant="primary" onClick={action}>
Added
<Image alt="check-white" height="18" src="/check-white.svg" width="18" />
</Button>
)}

{state === EProjectState.SUBMITTED && (
<Button size="sm" variant="disabled">
Submitted
</Button>
)}
</Skeleton>
</div>
)}
</div>
</article>
</Link>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -43,11 +42,7 @@ export const ProjectsResults = ({ pollId }: IProjectsResultsProps): JSX.Element
<InfiniteLoading
{...projects}
renderItem={(item, { isLoading }) => (
<Link
key={item.id}
className={clsx("relative", { "animate-pulse": isLoading })}
href={`/rounds/${pollId}/${item.id}`}
>
<div key={item.id} className={clsx("relative", { "animate-pulse": isLoading })}>
{!results.isLoading && roundState === ERoundState.RESULTS ? (
<ProjectItemAwarded amount={results.data?.projects[item.id]?.votes} />
) : null}
Expand All @@ -59,7 +54,7 @@ export const ProjectsResults = ({ pollId }: IProjectsResultsProps): JSX.Element
recipient={item}
state={EProjectState.SUBMITTED}
/>
</Link>
</div>
)}
/>
);
Expand Down
49 changes: 36 additions & 13 deletions packages/interface/src/features/rounds/components/Projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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]);

Expand All @@ -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();
Expand Down Expand Up @@ -118,24 +129,36 @@ export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => {
</div>
</div>

{roundState === ERoundState.APPLICATION && (
<div className="mb-4 flex w-full justify-end">
<Link href={`/rounds/${pollId}/applications/new`}>
<Button size="auto" variant="primary">
Create Application
</Button>
</Link>
{roundState === ERoundState.APPLICATION && address && (
<div className="mb-4 rounded-md border border-black p-4 dark:border-white">
<div className="flex justify-between">
<Heading size="xl">My Projects</Heading>

<Link href={`/rounds/${pollId}/applications/new`}>
<Button size="auto" variant="primary">
Create Application
</Button>
</Link>
</div>

<div className="my-4 gap-4 md:grid md:grid-cols-2 lg:grid lg:grid-cols-3">
{myApplications &&
myApplications.length > 0 &&
myApplications.map((project) => (
<ProjectItem key={project.id} isLoading={false} pollId={pollId} recipient={project} />
))}

{(!myApplications || myApplications.length === 0) && (
<p className="text-gray-400">Create your application by clicking the button</p>
)}
</div>
</div>
)}

<InfiniteLoading
{...projects}
renderItem={(item, { isLoading }) => (
<Link
key={item.id}
className={clsx("relative", { "animate-pulse": isLoading })}
href={`/rounds/${pollId}/${item.id}`}
>
<div key={item.id} className={clsx("relative", { "animate-pulse": isLoading })}>
{!results.isLoading && roundState === ERoundState.RESULTS ? (
<ProjectItemAwarded amount={results.data?.projects[item.id]?.votes} />
) : null}
Expand All @@ -147,7 +170,7 @@ export const Projects = ({ pollId = "" }: IProjectsProps): JSX.Element => {
recipient={item}
state={defineState(Number.parseInt(item.index, 10))}
/>
</Link>
</div>
)}
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/interface/src/hooks/useRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface SubmitApplicationArgs {
*/
registryAddress: Hex;
/**
* The recipient of the attestation
* The recipient of the application
*/
recipient: Hex;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useMemo } from "react";
import { FiAlertCircle } from "react-icons/fi";
Expand All @@ -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";
Expand Down Expand Up @@ -83,9 +82,7 @@ const ConfirmProjectPage = ({ pollId }: { pollId: string }): JSX.Element => {
</div>
)}

<Link href={`/rounds/${pollId}/${project.recipient.id}`}>
<ProjectItem isLoading={false} pollId={pollId} recipient={project.recipient as IRecipient} />
</Link>
<ProjectItem isLoading={false} pollId={pollId} recipient={project.recipient as IRecipient} />
</div>
</div>
</Layout>
Expand Down
Loading

0 comments on commit aca66f6

Please sign in to comment.