Skip to content

Commit

Permalink
Merge pull request #1302 from matiasbenary/feat/add-nft
Browse files Browse the repository at this point in the history
Mint nft tool added
  • Loading branch information
calebjacob authored Aug 30, 2024
2 parents b73641e + 95030e7 commit 2acf77e
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/assets/images/mintbase.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/images/paras.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/components/sidebar-navigation/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ export const Sidebar = () => {
</S.NavigationItem>
</Tooltip>

<Tooltip content="Tools" side="right" disabled={tooltipsDisabled}>
<S.NavigationItem $active={isNavigationItemActive('/tools')} $type="featured" href="/tools">
<i className="ph-wrench ph-bold" />
<span>Tools</span>
</S.NavigationItem>
</Tooltip>

<Tooltip content="Events" side="right" disabled={tooltipsDisabled}>
<S.NavigationItem $active={isNavigationItemActive('/events')} $type="featured" href="/events">
<i className="ph-calendar ph-bold" />
Expand Down
158 changes: 158 additions & 0 deletions src/components/tools/MintNft.tsx
Original file line number Diff line number Diff line change
@@ -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<FormData>();

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<FormData> = 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 (
<>
<Text size="text-l" style={{ marginBottom: '12px' }}>
{' '}
Mint NFT{' '}
</Text>
<Form onSubmit={handleSubmit(onSubmit)}>
<Flex stack gap="l">
<Input
label="Title"
placeholder="Enter title"
error={errors.title?.message}
{...register('title', { required: 'Title is required' })}
/>
<Input
label="Description"
placeholder="Enter description"
error={errors.description?.message}
{...register('description', { required: 'Description is required' })}
/>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Controller
control={control}
name="image"
rules={{
required: 'Image is required',
validate: validateImage,
}}
render={({ field, fieldState }) => (
<FileInput
label="Image Upload"
accept={ACCEPTED_IMAGE_TYPES.join(',')}
error={fieldState.error?.message}
{...field}
value={field.value ? Array.from(field.value) : []}
onChange={(value: File[] | null) => {
const files = value;
field.onChange(files);
}}
/>
)}
/>
<span style={{ fontSize: '0.8rem', color: 'gray' }}>
Accepted Formats: PNG, JPEG, GIF, SVG | Ideal dimension: 1:1 | Max size: 5MB
</span>
</div>
<Button label="Mint me" variant="affirmative" type="submit" loading={isSubmitting} />
</Flex>
</Form>
</>
);
};

export default MintNft;
61 changes: 61 additions & 0 deletions src/components/tools/NonFungibleToken.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<MintNft />
<Text size="text-l" style={{ margin: '12px 0 0 0' }}>
{' '}
Community tools{' '}
</Text>
<Text>For more advanced options use community tools:</Text>
<MintbaseButton href="https://paras.id/" target="_blank">
<Image alt="Mintbase Logo" src={MintBase} width={85} />{' '}
</MintbaseButton>
<ParasButton href="https://paras.id/" target="_blank">
<Image alt="Paras Logo" src={Paras} width={85} />{' '}
</ParasButton>
</>
);
};

export default NonFungibleToken;
73 changes: 73 additions & 0 deletions src/pages/tools.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Section grow="available" style={{ background: 'var(--sand3)' }}>
<Container size="s">
<Flex stack gap="l">
<Text as="h1" size="text-2xl">
Tools
</Text>

{signedAccountId ? (
<Card style={{ paddingTop: 0 }}>
<Tabs.Root value={selectedTab}>
<Tabs.List style={{ marginBottom: 'var(--gap-m)' }}>
<Tabs.Trigger href="?tab=ft" value="ft">
<SvgIcon icon={<Coin fill="bold" />} />
FT
</Tabs.Trigger>

<Tabs.Trigger href="?tab=nft" value="nft">
<SvgIcon icon={<ImagesSquare fill="bold" />} />
NFT
</Tabs.Trigger>

<Tabs.Trigger href="?tab=linkdrops" value="linkdrops">
<SvgIcon icon={<Gift fill="bold" />} />
Linkdrops
</Tabs.Trigger>
</Tabs.List>

<Tabs.Content value="ft">
<Text>Coming soon</Text>
</Tabs.Content>

<Tabs.Content value="nft">
<NonFungibleToken />
</Tabs.Content>

<Tabs.Content value="linkdrops">
<Text>Coming soon</Text>
</Tabs.Content>
</Tabs.Root>
</Card>
) : (
<Card>
<Text>Please sign in to use wallet utilities.</Text>
<Button label="Sign In" fill="outline" onClick={() => requestAuthentication()} />
</Card>
)}
</Flex>
</Container>
</Section>
);
};

ToolsPage.getLayout = useDefaultLayout;

export default ToolsPage;

0 comments on commit 2acf77e

Please sign in to comment.