Skip to content

Commit

Permalink
feat: edit application
Browse files Browse the repository at this point in the history
  • Loading branch information
kittybest committed Feb 4, 2025
1 parent f192101 commit 6051c7b
Show file tree
Hide file tree
Showing 11 changed files with 567 additions and 15 deletions.
4 changes: 3 additions & 1 deletion packages/interface/src/components/ImageUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { toast } from "sonner";
import { IconButton } from "~/components/ui/Button";

export interface IImageUploadProps extends ComponentProps<"img"> {
defaultValue?: string;
name?: string;
maxSize?: number;
}

export const ImageUpload = forwardRef(
(
{
defaultValue = "none",
name = "",
maxSize = 1024 * 1024, // 1 MB
className,
Expand Down Expand Up @@ -93,7 +95,7 @@ export const ImageUpload = forwardRef(
<div
className={clsx("h-full rounded-xl bg-gray-200 bg-cover bg-center bg-no-repeat")}
style={{
backgroundImage: select.data ? `url("${value}")` : "none",
backgroundImage: select.data ? `url("${value}")` : `url(${defaultValue})`,
}}
/>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
import { useRouter } from "next/router";
import { useState, useCallback } from "react";
import { toast } from "sonner";
import { useAccount } from "wagmi";

import { ImageUpload } from "~/components/ImageUpload";
import { FieldArray, Form, FormControl, FormSection, Select, Textarea } from "~/components/ui/Form";
import { Input } from "~/components/ui/Input";
import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork";

import type { IRecipient } from "~/utils/types";

import { useEditProposal } from "../hooks/useEditProposal";
import { MetadataSchema, contributionTypes, fundingSourceTypes, type Metadata } from "../types";

import { ImpactTags } from "./ImpactTags";
import { MetadataButtons, EMetadataStep } from "./MetadataButtons";
import { MetadataSteps } from "./MetadataSteps";
import { ReviewProposalDetails } from "./ReviewProposalDetails";

interface IEditMetadataFormProps {
pollId: string;
project?: IRecipient;
}

export const EditMetadataForm = ({ pollId, project = undefined }: IEditMetadataFormProps): JSX.Element => {
const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork();

const { address } = useAccount();

const router = useRouter();

/**
* There are 3 steps for creating a project proposal.
* The first step is to set the project introduction (profile);
* the second step is to set the contributions, impacts, and funding sources (advanced);
* the last step is to review the input values, allow editing by going back to previous steps (review).
*/
const [step, setStep] = useState<EMetadataStep>(EMetadataStep.PROFILE);

const handleNextStep = useCallback(() => {
if (step === EMetadataStep.PROFILE) {
setStep(EMetadataStep.ADVANCED);
} else if (step === EMetadataStep.ADVANCED) {
setStep(EMetadataStep.REVIEW);
}
}, [step, setStep]);

const handleBackStep = useCallback(() => {
if (step === EMetadataStep.REVIEW) {
setStep(EMetadataStep.ADVANCED);
} else if (step === EMetadataStep.ADVANCED) {
setStep(EMetadataStep.PROFILE);
}
}, [step, setStep]);

const create = useEditProposal({
onSuccess: (id: bigint) => {
router.push(`/rounds/${pollId}/proposals/confirmation?id=${id.toString()}`);
},
onError: (err: { message: string }) =>
toast.error("Proposal create error", {
description: err.message,
}),
pollId,
});

const handleSubmit = useCallback(
(metadata: Metadata) => {
create.mutate(metadata);
},
[create],
);

const { error: createError } = create;

return (
<div className="dark:border-lighterBlack rounded-lg border border-gray-200 p-4">
<MetadataSteps step={step} />

<Form
defaultValues={{
payoutAddress: address,
name: project?.metadata?.name,
bio: project?.metadata?.bio,
websiteUrl: project?.metadata?.websiteUrl,
twitter: project?.metadata?.twitter,
github: project?.metadata?.github,
contributionDescription: project?.metadata?.contributionDescription,
impactDescription: project?.metadata?.impactDescription,
impactCategory: project?.metadata?.impactCategory,
profileImageUrl: project?.metadata?.profileImageUrl,
bannerImageUrl: project?.metadata?.bannerImageUrl,
contributionLinks: project?.metadata?.contributionLinks,
fundingSources: project?.metadata?.fundingSources,
}}
schema={MetadataSchema}
onSubmit={handleSubmit}
>
<FormSection
className={step === EMetadataStep.PROFILE ? "block" : "hidden"}
description="Please provide information about your project."
title="Project Profile"
>
<FormControl required hint="This is the name of your project" label="Project name" name="name">
<Input placeholder="Type your project name" />
</FormControl>

<FormControl required label="Description" name="bio">
<Textarea placeholder="Type project description" rows={4} />
</FormControl>

<small className="-mt-4">*Markdown Supported</small>

<div className="gap-4 md:flex">
<FormControl required className="flex-1" label="Website" name="websiteUrl">
<Input placeholder="https://" />
</FormControl>

<FormControl required className="flex-1" label="Payout address" name="payoutAddress">
<Input placeholder="0x..." />
</FormControl>
</div>

<div className="gap-4 md:flex">
<FormControl className="flex-1" label="X(Twitter)" name="twitter" required={false}>
<Input placeholder="Type your twitter username" />
</FormControl>

<FormControl
className="flex-1"
hint="Provide your github of this project"
label="Github"
name="github"
required={false}
>
<Input placeholder="Type your github username" />
</FormControl>
</div>

<div className="mb-4 flex flex-col gap-4 sm:flex-row">
<FormControl
required
hint="The size should be smaller than 1MB."
label="Project avatar"
name="profileImageUrl"
>
<ImageUpload
className="h-48 w-48"
defaultValue={project?.metadata?.profileImageUrl}
name="profileImageUrl"
/>
</FormControl>

<FormControl
required
className="flex-1"
hint="The size should be smaller than 1MB."
label="Project background image"
name="bannerImageUrl"
>
<ImageUpload className="h-48" defaultValue={project?.metadata?.bannerImageUrl} name="bannerImageUrl" />
</FormControl>
</div>
</FormSection>

<FormSection
className={step === EMetadataStep.ADVANCED ? "block" : "hidden"}
description="Describe the contribution and impact of your project."
title="Contribution & Impact"
>
<FormControl required label="Contribution description" name="contributionDescription">
<Textarea placeholder="What have your project contributed to?" rows={4} />
</FormControl>

<small className="-mt-4">*Markdown Supported</small>

<FormControl required label="Impact description" name="impactDescription">
<Textarea placeholder="What impact has your project had?" rows={4} />
</FormControl>

<small className="-mt-4">*Markdown Supported</small>

<ImpactTags />

<FieldArray
description="Where can we find your contributions?"
name="contributionLinks"
renderField={(field, i) => (
<div className="mb-4 flex flex-wrap gap-2">
<FormControl required className="min-w-96" name={`contributionLinks.${i}.description`}>
<Input placeholder="Type the description of your contribution" />
</FormControl>

<FormControl required className="min-w-72" name={`contributionLinks.${i}.url`}>
<Input placeholder="https://" />
</FormControl>

<FormControl required name={`contributionLinks.${i}.type`}>
<Select>
{Object.entries(contributionTypes).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</Select>
</FormControl>
</div>
)}
title="Contribution links"
/>

<FieldArray
description="From what sources have you received funding?"
name="fundingSources"
renderField={(field, i) => (
<div className="mb-4 flex flex-wrap gap-2">
<FormControl required className="min-w-96" name={`fundingSources.${i}.description`}>
<Input placeholder="Type the name of your funding source" />
</FormControl>

<FormControl required valueAsNumber className="w-32" name={`fundingSources.${i}.amount`}>
<Input placeholder="Amount" type="number" />
</FormControl>

<FormControl required className="w-32" name={`fundingSources.${i}.currency`}>
<Input placeholder="e.g. USD" />
</FormControl>

<FormControl required name={`fundingSources.${i}.type`}>
<Select>
{Object.entries(fundingSourceTypes).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</Select>
</FormControl>
</div>
)}
title="Funding sources"
/>
</FormSection>

{step === EMetadataStep.REVIEW && <ReviewProposalDetails />}

{step === EMetadataStep.REVIEW && (
<div className="mb-2 w-full text-right text-sm italic text-blue-400">
{!address && <p>You must connect wallet to create a proposal of a project</p>}

{!isCorrectNetwork && <p className="gap-2">You must be connected to {correctNetwork.name}</p>}

{createError && (
<p>
Make sure you&apos;re not connected to a VPN since this can cause problems with the RPC and your wallet.
</p>
)}
</div>
)}

<MetadataButtons
isPending={create.isPending}
isUploading={create.isPending}
step={step}
onBackStep={handleBackStep}
onNextStep={handleNextStep}
/>
</Form>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const MetadataForm = ({ pollId }: IMetadataFormProps): JSX.Element => {
label="Project avatar"
name="profileImageUrl"
>
<ImageUpload className="h-48 w-48" />
<ImageUpload className="h-48 w-48" name="profileImageUrl" />
</FormControl>

<FormControl
Expand All @@ -140,7 +140,7 @@ export const MetadataForm = ({ pollId }: IMetadataFormProps): JSX.Element => {
label="Project background image"
name="bannerImageUrl"
>
<ImageUpload className="h-48" />
<ImageUpload className="h-48" name="bannerImageUrl" />
</FormControl>
</div>
</FormSection>
Expand Down
20 changes: 20 additions & 0 deletions packages/interface/src/features/proposals/components/ReviewBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Link from "next/link";
import { useMemo, useCallback, useEffect } from "react";
import { FiAlertCircle } from "react-icons/fi";
import { zeroAddress } from "viem";
import { useAccount } from "wagmi";

import { StatusBar } from "~/components/StatusBar";
import { Button } from "~/components/ui/Button";
Expand Down Expand Up @@ -31,13 +33,23 @@ export const ReviewBar = ({ pollId, projectId }: IReviewBarProps): JSX.Element =
const proposal = useRequestByProjectId(projectId, round?.registryAddress ?? zeroAddress);
const approve = useApproveRequest({});

const { address } = useAccount();

const isApproved = useMemo(() => {
if (project.data && (project.data as unknown as IRecipient).initialized) {
return true;
}
return false;
}, [project.data, approve.isSuccess, approve.isError]);

const isOwner = useMemo(() => {
if (project.data && project.data.payout === address?.toLowerCase()) {
return true;
}

return false;
}, [project, address]);

// approve the proposal
const onClick = useCallback(() => {
if (!proposal.data) {
Expand Down Expand Up @@ -81,6 +93,14 @@ export const ReviewBar = ({ pollId, projectId }: IReviewBarProps): JSX.Element =
</Button>
</div>
)}

{isOwner && isApproved && (
<div className="flex justify-end">
<Button as={Link} href={`/rounds/${pollId}/proposals/edit?id=${projectId}`} size="auto" variant="primary">
Edit Proposal
</Button>
</div>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type TUseCreateProposalReturn = Omit<UseMutationResult<bigint, Error | Tr
};

/**
* Hook to create a propject proposal
* Hook to create a project proposal
*
* @param options - Options for the hook
* @returns the result of the mutation
Expand Down
Loading

0 comments on commit 6051c7b

Please sign in to comment.