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 + + + + + + > + ); +}; + +export default MintNft; diff --git a/src/components/tools/NonFungibleToken.tsx b/src/components/tools/NonFungibleToken.tsx new file mode 100644 index 000000000..00159ea0e --- /dev/null +++ b/src/components/tools/NonFungibleToken.tsx @@ -0,0 +1,61 @@ +import { Text } from '@near-pagoda/ui'; +import Image from 'next/image'; +import styled from 'styled-components'; + +import MintBase from '@/assets/images/mintbase.svg'; +import Paras from '@/assets/images/paras.svg'; + +import MintNft from './MintNft'; + +const StyledButton = styled.a` + background-color: #1e2030; + color: #fff; + border: none; + border-radius: 25px; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.3s; + + &:hover { + background-color: #101124; + } +`; + +const MintbaseButton = styled(StyledButton)` + background-color: #1e2030; + &:hover { + background-color: #282b3b; + } +`; + +const ParasButton = styled(StyledButton)` + background-color: #050330; + &:hover { + background-color: #101438; + } +`; + +const NonFungibleToken = () => { + return ( + <> + + + {' '} + Community tools{' '} + + For more advanced options use community tools: + + {' '} + + + {' '} + + > + ); +}; + +export default NonFungibleToken; diff --git a/src/pages/applications.tsx b/src/pages/applications.tsx index 791fc9500..1db0ac1e1 100644 --- a/src/pages/applications.tsx +++ b/src/pages/applications.tsx @@ -40,6 +40,7 @@ const ApplicationsPage: NextPageWithLayout = () => { src={components.applicationsPage} meta={{ title: 'NEAR | Applications', description: 'Featured applications built on NEAR' }} componentProps={{ + ...router.query, setURLSearchParams, scrollTo, }} diff --git a/src/pages/tools.tsx b/src/pages/tools.tsx new file mode 100644 index 000000000..4a762a96a --- /dev/null +++ b/src/pages/tools.tsx @@ -0,0 +1,73 @@ +import { Button, Card, Container, Flex, Section, SvgIcon, Tabs, Text } from '@near-pagoda/ui'; +import { Coin, Gift, ImagesSquare } from '@phosphor-icons/react'; +import { useRouter } from 'next/router'; +import { useContext } from 'react'; + +import NonFungibleToken from '@/components/tools/NonFungibleToken'; +import { NearContext } from '@/components/WalletSelector'; +import { useDefaultLayout } from '@/hooks/useLayout'; +import { useSignInRedirect } from '@/hooks/useSignInRedirect'; +import type { NextPageWithLayout } from '@/utils/types'; + +const ToolsPage: NextPageWithLayout = () => { + const router = useRouter(); + const selectedTab = (router.query.tab as string) || 'ft'; + const { signedAccountId } = useContext(NearContext); + + const { requestAuthentication } = useSignInRedirect(); + return ( + + + + + Tools + + + {signedAccountId ? ( + + + + + } /> + FT + + + + } /> + NFT + + + + } /> + Linkdrops + + + + + Coming soon + + + + + + + + Coming soon + + + + ) : ( + + Please sign in to use wallet utilities. + requestAuthentication()} /> + + )} + + + + ); +}; + +ToolsPage.getLayout = useDefaultLayout; + +export default ToolsPage;