diff --git a/packages/nextjs/app/api/builders/[builderAddress]/route.ts b/packages/nextjs/app/api/builders/[builderAddress]/route.ts new file mode 100644 index 0000000..5116b56 --- /dev/null +++ b/packages/nextjs/app/api/builders/[builderAddress]/route.ts @@ -0,0 +1,17 @@ +import { NextResponse } from "next/server"; +import { findUserByAddress } from "~~/services/database/users"; + +export async function GET(_request: Request, { params }: { params: { builderAddress: string } }) { + try { + const builderAddress = params.builderAddress; + const builderData = await findUserByAddress(builderAddress); + return NextResponse.json(builderData); + } catch (error) { + return NextResponse.json( + { message: "Internal Server Error" }, + { + status: 500, + }, + ); + } +} diff --git a/packages/nextjs/app/submit-grant/_actions/index.ts b/packages/nextjs/app/submit-grant/_actions/index.ts new file mode 100644 index 0000000..434020a --- /dev/null +++ b/packages/nextjs/app/submit-grant/_actions/index.ts @@ -0,0 +1,51 @@ +"use server"; + +import { verifyMessage } from "viem"; +import { createGrant } from "~~/services/database/grants"; +import { findUserByAddress } from "~~/services/database/users"; + +type SignatureAndSigner = { + signature?: `0x${string}`; + address?: string; +}; + +export const submitGrantAction = async ({ signature, address }: SignatureAndSigner, form: FormData) => { + try { + const formData = Object.fromEntries(form.entries()); + if (!formData.title || !formData.description || !formData.askAmount) { + throw new Error("Invalid form data"); + } + + if (!signature || !address) { + throw new Error("Signature and address are required to submit grant"); + } + + const constructedMessage = JSON.stringify(formData); + const isMessageValid = await verifyMessage({ message: constructedMessage, signature, address }); + if (!isMessageValid) { + throw new Error("Invalid signature"); + } + + // Verif if the builder is present + const builder = await findUserByAddress(address); + if (!builder.exists) { + throw new Error("Only buidlguild builders can submit for grants"); + } + + // Save the form data to the database + const grant = await createGrant({ + title: formData.title as string, + description: formData.description as string, + askAmount: Number(formData.askAmount), + builder: address, + }); + + return grant; + } catch (e) { + if (e instanceof Error) { + throw e; + } + + throw new Error("Error processing form"); + } +}; diff --git a/packages/nextjs/app/submit-grant/_component/Form.tsx b/packages/nextjs/app/submit-grant/_component/Form.tsx new file mode 100644 index 0000000..6ca879f --- /dev/null +++ b/packages/nextjs/app/submit-grant/_component/Form.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { submitGrantAction } from "../_actions"; +import SubmitButton from "./SubmitButton"; +import { useAccount, useSignMessage } from "wagmi"; +import { notification } from "~~/utils/scaffold-eth"; + +const selectOptions = [0.1, 0.25, 0.5, 1]; + +const Form = () => { + const { signMessageAsync } = useSignMessage(); + const { address: connectedAddress } = useAccount(); + const router = useRouter(); + + const clientFormAction = async (formData: FormData) => { + try { + const formState = Object.fromEntries(formData.entries()); + if (formState.title === "" || formState.description === "") { + notification.error("Title and description are required"); + return; + } + + const signature = await signMessageAsync({ message: JSON.stringify(formState) }); + const signedMessageObject = { + signature: signature, + address: connectedAddress, + }; + + // server action + const submitGrantActionWithSignedMessage = submitGrantAction.bind(null, signedMessageObject); + await submitGrantActionWithSignedMessage(formData); + + notification.success("Proposal submitted successfully!"); + router.push("/"); + } catch (error: any) { + if (error instanceof Error) { + notification.error(error.message); + return; + } + notification.error("Something went wrong"); + } + }; + + return ( +
+
+

Submit Proposal

+
+

Title

+
+ +
+
+
+

Description

+
+