From 868321b7b704a339bedc8d1218eea42cacd819f4 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Wed, 18 Sep 2024 11:39:49 -0300 Subject: [PATCH 01/12] feat: add NFTs for linkdrops --- next.config.js | 2 +- src/components/NTFImage.tsx | 22 +- .../tools/Linkdrops/CreateNFTDrop.tsx | 208 ++++++++++++++++++ .../tools/Linkdrops/CreateTokenDrop.tsx | 4 +- src/components/tools/Linkdrops/index.tsx | 19 +- src/hooks/useNFT.ts | 85 +++++++ 6 files changed, 333 insertions(+), 7 deletions(-) create mode 100644 src/components/tools/Linkdrops/CreateNFTDrop.tsx create mode 100644 src/hooks/useNFT.ts diff --git a/next.config.js b/next.config.js index 9087c1113..2562d36e3 100644 --- a/next.config.js +++ b/next.config.js @@ -4,7 +4,7 @@ const nextConfig = { compiler: { styledComponents: true }, reactStrictMode: true, images: { - domains: ['ipfs.near.social'], + domains: ['ipfs.near.social','ipfs.io'], }, experimental: { optimizePackageImports: ['@phosphor-icons/react'], diff --git a/src/components/NTFImage.tsx b/src/components/NTFImage.tsx index effe6dba7..dd3656d9e 100644 --- a/src/components/NTFImage.tsx +++ b/src/components/NTFImage.tsx @@ -22,6 +22,15 @@ interface NftImageProps { const DEFAULT_IMAGE = 'https://ipfs.near.social/ipfs/bafkreibmiy4ozblcgv3fm3gc6q62s55em33vconbavfd2ekkuliznaq3zm'; +const getImage = (key: string) => { + const imgUrl = localStorage.getItem(`keysImage:${key}`); + return imgUrl || null; +}; + +const setImage = (key: string, url: string) => { + localStorage.setItem(`keysImage:${key}`, url); +}; + export const NftImage: React.FC = ({ nft, ipfs_cid, alt }) => { const { wallet } = useContext(NearContext); const [imageUrl, setImageUrl] = useState(DEFAULT_IMAGE); @@ -29,13 +38,17 @@ export const NftImage: React.FC = ({ nft, ipfs_cid, alt }) => { const fetchNftData = useCallback(async () => { if (!wallet || !nft || !nft.contractId || !nft.tokenId || ipfs_cid) return; + const imgCache = getImage(nft.tokenId); + if (imgCache) { + setImageUrl(imgCache); + return; + } const [nftMetadata, tokenData] = await Promise.all([ wallet.viewMethod({ contractId: nft.contractId, method: 'nft_metadata' }), wallet.viewMethod({ contractId: nft.contractId, method: 'nft_token', args: { token_id: nft.tokenId } }), ]); - const tokenMetadata = tokenData.metadata; - const tokenMedia = tokenMetadata?.media || ''; + const tokenMedia = tokenData?.metadata?.media || ''; if (tokenMedia.startsWith('https://') || tokenMedia.startsWith('http://') || tokenMedia.startsWith('data:image')) { setImageUrl(tokenMedia); @@ -54,5 +67,10 @@ export const NftImage: React.FC = ({ nft, ipfs_cid, alt }) => { } }, [ipfs_cid, fetchNftData]); + useEffect(() => { + if (!wallet || !nft || !nft.contractId || !nft.tokenId || ipfs_cid || DEFAULT_IMAGE === imageUrl) return; + setImage(nft.tokenId, imageUrl); + }, [imageUrl, wallet, nft, ipfs_cid]); + return ; }; diff --git a/src/components/tools/Linkdrops/CreateNFTDrop.tsx b/src/components/tools/Linkdrops/CreateNFTDrop.tsx new file mode 100644 index 000000000..868b681d5 --- /dev/null +++ b/src/components/tools/Linkdrops/CreateNFTDrop.tsx @@ -0,0 +1,208 @@ +import { Accordion, Button, Flex, Form, Input, openToast, Text } from '@near-pagoda/ui'; +import { parseNearAmount } from 'near-api-js/lib/utils/format'; +import { useContext, useState } from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; +import styled from 'styled-components'; + +import { NftImage } from '@/components/NTFImage'; +import type { NFT } from '@/hooks/useNFT'; +import useNFT from '@/hooks/useNFT'; +import generateAndStore from '@/utils/linkdrops'; + +import { NearContext } from '../../WalletSelector'; + +const CarouselContainer = styled.div` + display: flex; + overflow-x: auto; + width: 100%; + scrollbar-width: thin; + &::-webkit-scrollbar { + height: 8px; + } + &::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.3); + border-radius: 4px; + } +`; + +const ImgCard = styled.div<{ + selected: boolean; +}>` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 8px; + margin: 4px; + border-radius: 6px; + cursor: pointer; + border: ${(p) => (p.selected ? 'solid 1px #878782' : '')}; +`; + +const KEYPOM_CONTRACT_ADDRESS = 'v2.keypom.near'; + +type FormData = { + dropName: string; + amountPerLink: number; + tokenId: string; + senderId: string; + contractId: string; +}; + +const parseToNFTimage = (nft: NFT, origin: string) => { + return { + contractId: origin, + tokenId: nft.token_id, + }; +}; + +const getDeposit = (amountPerLink: number, numberLinks: number) => + parseNearAmount(((0.0426 + amountPerLink) * numberLinks).toString()); + +const CreateNFTDrop = () => { + const { wallet, signedAccountId } = useContext(NearContext); + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + setValue, + } = useForm({ + defaultValues: { + senderId: signedAccountId, + }, + }); + + const [nftSelected, setNftSelected] = useState(''); + + const { tokens } = useNFT(); + + const fillForm = (origin: string, nft: NFT) => () => { + setNftSelected(nft.token_id); + setValue('tokenId', nft.token_id); + setValue('contractId', origin); + }; + const onSubmit: SubmitHandler = async (data) => { + if (!wallet) throw new Error('Wallet has not initialized yet'); + const dropId = Date.now().toString(); + const args = { + deposit_per_use: '0', + drop_id: dropId, + metadata: JSON.stringify({ + dropName: data.dropName, + }), + public_keys: generateAndStore(data.dropName, 1), + nft: { + sender_id: data.senderId, + contract_id: data.contractId, + }, + }; + + await wallet.signAndSendTransactions({ + transactions: [ + { + receiverId: KEYPOM_CONTRACT_ADDRESS, + actions: [ + { + type: 'FunctionCall', + params: { + methodName: 'create_drop', + args, + gas: '300000000000000', + deposit: getDeposit(1, 1), + }, + }, + ], + }, + { + receiverId: data.contractId, + actions: [ + { + type: 'FunctionCall', + params: { + methodName: 'nft_transfer_call', + args: { + receiver_id: KEYPOM_CONTRACT_ADDRESS, + token_id: data.tokenId, + msg: dropId, + }, + gas: '300000000000000', + deposit: 1, + }, + }, + ], + }, + ], + }); + + openToast({ + type: 'success', + title: 'Form Submitted', + description: 'Your form has been submitted successfully', + duration: 5000, + }); + }; + return ( + <> + NFT Drop +
+ + + + {tokens.map((token, index) => { + return ( + + {token.origin} + + + + {token.nfts.map((nft) => { + return ( + + + {nft.metadata.title} + + ); + })} + + + + ); + })} + + + + +