diff --git a/src/app/create-rare-item/page.tsx b/src/app/create-rare-item/page.tsx index c210806..9238bc8 100644 --- a/src/app/create-rare-item/page.tsx +++ b/src/app/create-rare-item/page.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useState, useRef, ChangeEvent } from "react"; +import React, { useState, useRef, ChangeEvent, useEffect } from "react"; import { Button, Checkbox, @@ -45,16 +45,18 @@ export default function CreateRareItem() { defaultValues: { name: "", category: [], - description: "", price: "", - royality: "", - product_info: "", image: "", - brand_name: "", - tags: [], }, }); + useEffect(() => { + const subscription = form.watch((value, { name, type }) => + console.log("Form updated:", { field: name, type, value, errors: form.formState.errors }) + ); + return () => subscription.unsubscribe(); + }, [form]); + const uploadFile = async (fileToUpload: File) => { try { setUploading(true); @@ -97,32 +99,41 @@ export default function CreateRareItem() { }; async function onSubmit(values: z.infer) { + console.log("Form values:", values); + console.log("Form errors:", form.formState.errors); + console.log("CIDs:", cids); + if (cids.length === 0) { setImageError(true); + toast.error("Please upload at least one image"); return; } try { const brand_name = localStorage.getItem("brand_name"); - const phygitalData: PhygitalData = { + + if (!brand_name) { + toast.error("Brand name not found. Please create a brand first."); + return; + } + + const phygitalData: Partial = { type: "rare", name: values.name, - brand_name: brand_name || "", + brand_name: brand_name, category: values.category, - description: values.description, price: values.price, - royality: values.royality, - product_info: values.product_info, images: cids.map((cid) => "ipfs://" + cid), tags: tags, }; + console.log("Saving phygital data:", phygitalData); localStorage.setItem("phygitalData", JSON.stringify(phygitalData)); setLoading(true); router.push("/create-phygital-detail"); } catch (error) { console.error("Error:", error); - toast.error("Failed to save data"); + toast.error("Failed to save data: " + (error as Error).message); } finally { setLoading(false); } @@ -140,7 +151,12 @@ export default function CreateRareItem() {
- + { + console.log("Form validation failed:", errors); + toast.error("Please fill in all required fields"); + })} + >
{loading ? "Loading..." : "Next"} diff --git a/src/components/BrandForm.tsx b/src/components/BrandForm.tsx index 34ad77f..0cd3285 100644 --- a/src/components/BrandForm.tsx +++ b/src/components/BrandForm.tsx @@ -24,9 +24,18 @@ import { useRouter } from "next/navigation"; import { useAccount } from "wagmi"; import axios from "axios"; import { v4 as uuidv4 } from "uuid"; -import { clusterApiUrl, Connection, PublicKey, LAMPORTS_PER_SOL, Keypair } from "@solana/web3.js"; +import { + clusterApiUrl, + Connection, + PublicKey, + LAMPORTS_PER_SOL, + Keypair, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; import { useWallet } from "@solana/wallet-adapter-react"; -import { Metaplex, walletAdapterIdentity } from "@metaplex-foundation/js"; +import { Metaplex, walletAdapterIdentity, toBigNumber } from "@metaplex-foundation/js"; import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; interface BrandFormProps { @@ -245,71 +254,86 @@ export default function CreateBrand({ const deployContract = async () => { if (!publicKey || !metaplex) { - toast.error("Please connect your wallet first"); - return; + toast.error("Please connect your wallet first"); + return; } try { - // Check SOL balance - const balance = await connection.getBalance(publicKey); - if (balance < LAMPORTS_PER_SOL * 0.05) { - toast.error("Insufficient SOL balance. Need at least 0.05 SOL"); - return; - } - - // Generate a new keypair for the mint - const mintKeypair = Keypair.generate(); + // Check SOL balance + const balance = await connection.getBalance(publicKey); + const MINIMUM_BALANCE = LAMPORTS_PER_SOL * 0.05; // 0.05 SOL + if (balance < MINIMUM_BALANCE) { + toast.error("Insufficient SOL balance. Need at least 0.05 SOL"); + return; + } - // Create metadata - const metadata = { - name: form.getValues("name"), - symbol: "BRAND", - description: form.getValues("description"), - image: `ipfs://${cid}`, - external_url: getWebsiteUrl(), - attributes: [ - { - trait_type: "Brand Representative", - value: form.getValues("representative"), - }, - { - trait_type: "Region", - value: elevateRegion || "Global", - }, - ], - }; + // Create metadata + const metadata = { + name: form.getValues("name"), + symbol: "BRAND", + description: form.getValues("description"), + image: `ipfs://${cid}`, + external_url: getWebsiteUrl(), + attributes: [ + { + trait_type: "Brand Representative", + value: form.getValues("representative"), + }, + { + trait_type: "Region", + value: elevateRegion || "Global", + }, + ], + }; - // Upload to IPFS - const { IpfsHash } = await uploadToIPFS(metadata); - const metadataUri = `ipfs://${IpfsHash}`; + // Upload to IPFS + const { IpfsHash } = await uploadToIPFS(metadata); + const metadataUri = `ipfs://${IpfsHash}`; - // Create Brand Collection - const { nft: collectionNft } = await metaplex.nfts().create({ + // Create Brand Collection with retries + let retries = 3; + let lastError; + + while (retries > 0) { + try { + const { nft: collectionNft } = await metaplex.nfts().create({ uri: metadataUri, name: form.getValues("name"), sellerFeeBasisPoints: 500, // 5% royalty symbol: "BRAND", isCollection: true, - useNewMint: mintKeypair, creators: [ - { - address: publicKey, - share: 100, - authority: metaplex.identity(), - } + { + address: publicKey, + share: 100, + authority: metaplex.identity(), + }, ], isMutable: true, - }); + maxSupply: toBigNumber(0), + }); - // Store collection address for future reference - setIsDeployed(true); - return collectionNft.address.toString(); + // Store collection address for future reference + setIsDeployed(true); + return collectionNft.address.toString(); + } catch (error) { + lastError = error; + retries--; + if (retries > 0) { + console.warn(`NFT creation failed, retrying... (${retries} attempts left)`); + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second before retry + } + } + } + + // If we get here, all retries failed + throw lastError; } catch (error) { - console.error("Deployment error:", error); - toast.error(getErrorMessage(error)); - throw error; + console.error("Deployment error:", error); + toast.error(getErrorMessage(error)); + throw error; } -}; + }; const deployTradehubContract = async ( platformFee: number, @@ -471,12 +495,15 @@ export default function CreateBrand({ } try { - const socialLinksObject = socialLinks.reduce((acc, link) => { - if (link.platform && link.url) { - acc[link.platform] = link.url; - } - return acc; - }, {} as Record); + const socialLinksObject = socialLinks.reduce( + (acc, link) => { + if (link.platform && link.url) { + acc[link.platform] = link.url; + } + return acc; + }, + {} as Record + ); if (cid) { values.logo_image = "ipfs://" + cid; @@ -532,7 +559,10 @@ export default function CreateBrand({ toast.warning("Deploying brand collection..."); const collectionAddress = await deployContract(); - localStorage.setItem("AccessMasterAddress", collectionAddress as string); + localStorage.setItem( + "AccessMasterAddress", + collectionAddress as string + ); console.log("Brand collection deployed at:", collectionAddress); toast.warning("Deploying trading collection..."); @@ -585,7 +615,10 @@ export default function CreateBrand({ const brand = await response.json(); localStorage.setItem("BrandId", brand.id); - localStorage.setItem('webxr-experience-with-ai-avatar', values.webxr_experience_with_ai_avatar.toString()) + localStorage.setItem( + "webxr-experience-with-ai-avatar", + values.webxr_experience_with_ai_avatar.toString() + ); const users = await fetch(`${apiUrl}/users`, { method: "POST", @@ -1173,8 +1206,8 @@ export default function CreateBrand({ {loading ? "Processing..." : isEdit - ? "Update brand" - : "Continue"} + ? "Update brand" + : "Continue"}
@@ -1183,4 +1216,4 @@ export default function CreateBrand({ ); -} \ No newline at end of file +} diff --git a/src/utils/phygitals.ts b/src/utils/phygitals.ts index f73d1b5..a08a00f 100644 --- a/src/utils/phygitals.ts +++ b/src/utils/phygitals.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -// Base schema for both phygital and rare items +// Initial form schema for rare items (first page) export const baseFormSchema = z.object({ name: z .string() @@ -10,7 +10,15 @@ export const baseFormSchema = z.object({ .regex(/^[a-zA-Z0-9\s]*$/, { message: "Name must only contain letters and numbers", }), - category: z.array(z.string()) || z.string(), + category: z.array(z.string()), + price: z.string().min(1, { message: "Price must be provided" }), + image: z.string().optional(), + brand_name: z.string().optional(), + tags: z.array(z.string()).optional(), +}); + +// Details form schema (second page) +export const detailsFormSchema = z.object({ description: z .string() .min(2, { @@ -19,17 +27,7 @@ export const baseFormSchema = z.object({ .max(1000, { message: "Description should be less than 1000 words", }), - price: z.string().min(1, { message: "Price must be provided" }), - royality: z.string(), - product_info: z.string().min(2, { - message: "Product Information must be at least 2 characters", - }), - image: z.string(), - brand_name: z.string(), - tags: z.array(z.string()).optional(), - quantity: z.number(), color: z.string().min(1, "Color is required"), - size_option: z.number(), size_details: z .array( z.object({ @@ -39,24 +37,16 @@ export const baseFormSchema = z.object({ }) ) .optional(), -}); - -// Details form schema -export const detailsFormSchema = z.object({ - color: z.string().min(1, "Color is required"), - size_details: z.array( - z.object({ - size: z.string(), - quantity: z.number(), - additional_details: z.string().optional(), - }) - ), weight: z.string().min(1, "Weight is required"), material: z.string().min(1, "Material is required"), usage: z.string().optional(), care_instructions: z.string().optional(), manufacturer: z.string().min(1, "Manufacturer is required"), origin_country: z.string().min(1, "Country of origin is required"), + product_info: z.string(), + royality: z.string(), + quantity: z.number(), + size_option: z.number(), }); export const categories = [