diff --git a/src/assets/images/mintbase.svg b/src/assets/images/mintbase.svg new file mode 100644 index 000000000..a5c0bd781 --- /dev/null +++ b/src/assets/images/mintbase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/paras.svg b/src/assets/images/paras.svg new file mode 100644 index 000000000..c2c8b549c --- /dev/null +++ b/src/assets/images/paras.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/sidebar-navigation/Sidebar.tsx b/src/components/sidebar-navigation/Sidebar.tsx index 2e1e9c2f3..7ada198bc 100644 --- a/src/components/sidebar-navigation/Sidebar.tsx +++ b/src/components/sidebar-navigation/Sidebar.tsx @@ -110,6 +110,13 @@ export const Sidebar = () => { + + + + Tools + + + diff --git a/src/components/tools/MintNft.tsx b/src/components/tools/MintNft.tsx new file mode 100644 index 000000000..7172677d7 --- /dev/null +++ b/src/components/tools/MintNft.tsx @@ -0,0 +1,158 @@ +import { Button, FileInput, Flex, Form, Input, openToast, Text } from '@near-pagoda/ui'; +import { useContext } from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { Controller, useForm } from 'react-hook-form'; + +import { NearContext } from '../WalletSelector'; + +type FormData = { + title: string; + description: string; + image: FileList; +}; + +interface IPFSResponse { + cid: string; +} + +const MAX_FILE_SIZE = 5 * 1024 * 1024; +const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml']; + +const MintNft = () => { + const { + control, + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm(); + + const { wallet, signedAccountId } = useContext(NearContext); + + const validateImage = (files: FileList) => { + if (files.length === 0) return 'Image is required'; + const file = files[0]; + if (file.size > MAX_FILE_SIZE) return 'Image size should be less than 5MB'; + if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) return 'Not a valid image format'; + return true; + }; + + const onSubmit: SubmitHandler = async (data) => { + if (!wallet) throw new Error('Wallet has not initialized yet'); + try { + let file = ''; + + if (data.image[0]) { + const res = await fetch('https://ipfs.near.social/add', { + method: 'POST', + headers: { Accept: 'application/json' }, + body: data.image[0], + }); + const fileData: IPFSResponse = await res.json(); + file = fileData.cid; + } + + const args = { + receiver_id: signedAccountId, + token_id: crypto.randomUUID(), + token_metadata: { + media: `https://ipfs.near.social/ipfs/${file}`, + title: data.title, + description: data.description, + }, + }; + + const string_args = JSON.stringify(args); + + // TODO: Improve, we estimate the cost as 3 times the cost of storing the args + const cost_per_byte = 10 ** 19; + const estimated_cost = string_args.length * cost_per_byte * 3; + + await wallet.signAndSendTransactions({ + transactions: [ + { + receiverId: 'nft.primitives.near', + actions: [ + { + type: 'FunctionCall', + params: { + methodName: 'nft_mint', + args, + gas: '300000000000000', + deposit: estimated_cost, + }, + }, + ], + }, + ], + }); + + openToast({ + type: 'success', + title: 'Form Submitted', + description: 'Your form has been submitted successfully', + duration: 5000, + }); + } catch (error) { + openToast({ + type: 'error', + title: 'Error', + description: 'Failed to submit form', + duration: 5000, + }); + } + }; + + return ( + <> + + {' '} + Mint NFT{' '} + +
+ + + +
+ ( + { + const files = value; + field.onChange(files); + }} + /> + )} + /> + + Accepted Formats: PNG, JPEG, GIF, SVG | Ideal dimension: 1:1 | Max size: 5MB + +
+