diff --git a/prisma/migrations/20240325152421_add_mint_request/migration.sql b/prisma/migrations/20240325152421_add_mint_request/migration.sql new file mode 100644 index 00000000..75e0c969 --- /dev/null +++ b/prisma/migrations/20240325152421_add_mint_request/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "MintRequest" ( + "id" SERIAL NOT NULL, + "userId" INTEGER NOT NULL, + "walletAddress" TEXT NOT NULL, + "stringifiedPublicKeys" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "MintRequest_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "MintRequest" ADD CONSTRAINT "MintRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fd58b56e..cba8f272 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -38,6 +38,7 @@ model User { admin Admin? buidlMint BuidlMint[] itemRedeemed ItemRedeemed[] + MintRequest MintRequest[] } model Admin { @@ -119,7 +120,7 @@ model Quest { summonId String? @unique buidlReward Int itemId Int? @unique - priority Int @default(0) + priority Int @default(0) isHidden Boolean @default(false) createdAt DateTime @default(now()) userRequirements UserRequirement[] @@ -248,3 +249,12 @@ model ItemRedeemed { user User @relation(fields: [userId], references: [id]) item Item @relation(fields: [itemId], references: [id]) } + +model MintRequest { + id Int @id @default(autoincrement()) + userId Int + walletAddress String + stringifiedPublicKeys String + createdAt DateTime @default(now()) + user User @relation(fields: [userId], references: [id]) +} diff --git a/src/pages/api/mint/art.ts b/src/pages/api/mint/art.ts new file mode 100644 index 00000000..b3d563dc --- /dev/null +++ b/src/pages/api/mint/art.ts @@ -0,0 +1,54 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import prisma from "@/lib/server/prisma"; +import { verifyAuthToken } from "@/lib/server/auth"; + +interface MintRequest { + authToken: string; + walletAddress: string; + stringifiedPublicKeys: string; +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + try { + const { authToken, walletAddress, stringifiedPublicKeys }: MintRequest = + req.body; + + if (!authToken || !walletAddress || !stringifiedPublicKeys) { + return res.status(400).json({ message: "Missing required fields" }); + } + + const userId = await verifyAuthToken(authToken); + if (!userId) { + return res.status(401).json({ message: "Invalid or expired authToken" }); + } + + const existingMintRequest = await prisma.mintRequest.findFirst({ + where: { userId }, + }); + if (existingMintRequest) { + return res + .status(400) + .json({ message: "User already has a mint request" }); + } + + await prisma.mintRequest.create({ + data: { + userId: userId, + walletAddress, + stringifiedPublicKeys, + }, + }); + + return res.status(200).json({}); + } catch (error) { + console.error("Request error: ", error); + res.status(500).json({ message: "Error processing request" }); + } +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 1f5b4c4e..26ccb7d3 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -510,7 +510,10 @@ export default function Social() { - {claveInfo?.claveWalletAddress ? ( + + + + {/* {claveInfo?.claveWalletAddress ? ( @@ -525,7 +528,7 @@ export default function Social() { ) : ( <> - )} + )} */} diff --git a/src/pages/mint/index.tsx b/src/pages/mint/index.tsx new file mode 100644 index 00000000..74c9279f --- /dev/null +++ b/src/pages/mint/index.tsx @@ -0,0 +1,103 @@ +import { Button } from "@/components/Button"; +import { Input } from "@/components/Input"; +import { FormStepLayout } from "@/layouts/FormStepLayout"; +import { + getAuthToken, + getLocationSignatures, + getUsers, +} from "@/lib/client/localStorage"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useState } from "react"; +import { toast } from "sonner"; + +const MintPage = () => { + const [walletAddress, setWalletAddress] = useState(""); + const [loading, setLoading] = useState(false); + const router = useRouter(); + + const handleMint = async (event: React.FormEvent) => { + event.preventDefault(); + if (!walletAddress || walletAddress.slice(0, 2) !== "0x") { + toast.error("Please enter a valid wallet address 0x..."); + return; + } + setLoading(true); + + const token = getAuthToken(); + if (!token) { + toast.error("You must be logged in to mint an NFT"); + setLoading(false); + return; + } + + const users = getUsers(); + const userSignaturePublicKeys: string[] = Object.values(users) + .filter((user) => user.sigPk && user.inTs) + .map((user) => user.sigPk!); + + const locations = getLocationSignatures(); + const locationSignaturePublicKeys: string[] = Object.values(locations) + .filter((location) => location.pk) + .map((location) => location.pk); + + const stringifiedPublicKeys = JSON.stringify({ + users: userSignaturePublicKeys, + locations: locationSignaturePublicKeys, + }); + + const response = await fetch("/api/mint/art", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + authToken: token.value, + walletAddress, + stringifiedPublicKeys, + }), + }); + + if (!response.ok) { + const error = await response.json(); + console.error("Error minting NFT: ", error.messsage); + toast.error("Error minting NFT! Please try again later."); + } else { + toast.info( + "Successfully processed mint request - please wait a few days for the NFT to be minted." + ); + } + setLoading(false); + + router.push("/"); + }; + + return ( + + + + Back + + + } + > + setWalletAddress(event.target.value)} + required + /> + + ); +}; +export default MintPage;