diff --git a/packages/nextjs/app/api/submissions/route.ts b/packages/nextjs/app/api/submissions/route.ts index 20d91d4..7672e06 100644 --- a/packages/nextjs/app/api/submissions/route.ts +++ b/packages/nextjs/app/api/submissions/route.ts @@ -1,5 +1,9 @@ import { NextResponse } from "next/server"; -import { getAllSubmissions } from "~~/services/database/repositories/submissions"; +import { recoverTypedDataAddress } from "viem"; +import { createBuilder, getBuilderById } from "~~/services/database/repositories/builders"; +import { createSubmission, getAllSubmissions } from "~~/services/database/repositories/submissions"; +import { SubmissionInsert } from "~~/services/database/repositories/submissions"; +import { EIP_712_DOMAIN, EIP_712_TYPES__SUBMISSION } from "~~/utils/eip712"; export async function GET() { try { @@ -10,3 +14,53 @@ export async function GET() { return NextResponse.json({ error: "Error fetching submissions" }, { status: 500 }); } } + +export type CreateNewSubmissionBody = SubmissionInsert & { signature: `0x${string}`; signer: string }; + +export async function POST(req: Request) { + try { + const { title, description, linkToRepository, signature, signer } = (await req.json()) as CreateNewSubmissionBody; + + if ( + !title || + !description || + !linkToRepository || + !signature || + !signer || + description.length > 750 || + title.length > 75 + ) { + return NextResponse.json({ error: "Invalid form details submitted" }, { status: 400 }); + } + + const recoveredAddress = await recoverTypedDataAddress({ + domain: EIP_712_DOMAIN, + types: EIP_712_TYPES__SUBMISSION, + primaryType: "Message", + message: { title, description, linkToRepository }, + signature: signature, + }); + + if (recoveredAddress !== signer) { + return NextResponse.json({ error: "Recovered address did not match signer" }, { status: 401 }); + } + + const builder = await getBuilderById(signer); + + if (!builder) { + await createBuilder({ id: signer, role: "user" }); + } + + const submission = await createSubmission({ + title: title, + description: description, + linkToRepository: linkToRepository, + builder: signer, + }); + + return NextResponse.json({ submission }, { status: 201 }); + } catch (e) { + console.error(e); + return NextResponse.json({ error: "Error processing form" }, { status: 500 }); + } +} diff --git a/packages/nextjs/app/submit/_component/Form.tsx b/packages/nextjs/app/submit/_component/Form.tsx new file mode 100644 index 0000000..e280f0a --- /dev/null +++ b/packages/nextjs/app/submit/_component/Form.tsx @@ -0,0 +1,116 @@ +"use client"; + +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import SubmitButton from "./SubmitButton"; +import { useMutation } from "@tanstack/react-query"; +import { useAccount, useSignTypedData } from "wagmi"; +import { CreateNewSubmissionBody } from "~~/app/api/submissions/route"; +import { EIP_712_DOMAIN, EIP_712_TYPES__SUBMISSION } from "~~/utils/eip712"; +import { postMutationFetcher } from "~~/utils/react-query"; +import { notification } from "~~/utils/scaffold-eth"; + +const MAX_DESCRIPTION_LENGTH = 750; + +const Form = () => { + const { address: connectedAddress } = useAccount(); + const [descriptionLength, setDescriptionLength] = useState(0); + const { signTypedDataAsync } = useSignTypedData(); + const router = useRouter(); + const { mutateAsync: postNewSubmission } = useMutation({ + mutationFn: (newSubmission: CreateNewSubmissionBody) => + postMutationFetcher("/api/submissions", { body: newSubmission }), + }); + + const clientFormAction = async (formData: FormData) => { + if (!connectedAddress) { + notification.error("Please connect your wallet"); + return; + } + + try { + const title = formData.get("title") as string; + const description = formData.get("description") as string; + const linkToRepository = formData.get("linkToRepository") as string; + if (!title || !description || !linkToRepository) { + notification.error("Please fill all the fields"); + return; + } + + const signature = await signTypedDataAsync({ + domain: EIP_712_DOMAIN, + types: EIP_712_TYPES__SUBMISSION, + primaryType: "Message", + message: { + title: title, + description: description, + linkToRepository: linkToRepository, + }, + }); + + await postNewSubmission({ title, description, linkToRepository, signature, signer: connectedAddress }); + + notification.success("Extension submitted successfully!"); + router.push("/"); + } catch (error: any) { + if (error instanceof Error) { + notification.error(error.message); + return; + } + notification.error("Something went wrong"); + } + }; + + return ( +
+
+

Submit Extension

+
+

Title

+
+ +
+
+
+

Description

+
+