# NFT frontend application
We'll create the application for the NFT contract we wrote in the [previous lesson](https://hackmd.io/rvQGQWnvSwC7MaXlMNhLhw?both).
### Create-NFT page
First clone the [frontend-starter](https://github.com/LouiseMedova/gear-app-starter). Install [NodeJs](https://nodejs.org/en/download/) and [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Make sure you have the latest LTS version of the NodeJs installed. Then install yarn:
```
npm install --global yarn
```
There is `.env.example` file. Create your own `.env` file and copy `.env.example` contents to your `.env`. It contains:
- `REACT_APP_NODE_ADDRESS` - this variable defines which node we'll work on.
- `REACT_APP_CONTRACT_ADDRESS` - the address of the contract uploaded to the chain.
- `REACT_APP_IPFS_ADDRESS` and `REACT_APP_IPFS_GATEWAY_ADDRESS` are variables which we will need when we upload media files to IPFS.
Upload the contract to the chain and set up the address in the `.env` file. Put `meta.txt` file to the folder `assets/meta` and `nft_state.meta.wasm` file to the `mkdir assets/wasm`.
Install packages:
```
yarn install
```
and run the app
```
yarn start
```
The main file `App.tsx` is simple:
```typescript
import { useApi, useAccount } from '@gear-js/react-hooks';
import { Routing } from 'pages';
import { Header, Footer, ApiLoader } from 'components';
import { withProviders } from 'hocs';
import 'App.scss';
function Component() {
const { isApiReady } = useApi();
const { isAccountReady } = useAccount();
const isAppReady = isApiReady && isAccountReady;
return (
<>
<Header isAccountVisible={isAccountReady} />
<main>{isAppReady ? <Routing /> : <ApiLoader />}</main>
<Footer />
</>
);
}
export const App = withProviders(Component);
```
It checks whether the application is connected to the chain:
```typescript
const { isApiReady } = useApi();
```
It checks whether the account is connected to the application through the web extension:
```typescript
const { isAccountReady } = useAccount();
```
If the `api` is ready and `account` is connected it displays the applications pages.
Let's go to `pages` folder. The `index.tsx` file is also simple:
```typescript
import { Route, Routes } from 'react-router-dom';
import { Home } from './home/Home';
const routes = [
{ path: '/', Page: Home },
];
export function Routing() {
const getRoutes = () => routes.map(({ path, Page }) =>
<Route key={path} path={path} element={<Page />} />
);
return <Routes>{getRoutes()}</Routes>;
}
```
Now it has only one page `Home`. Let's create a page for NFT creation.
```
mkdir src/pages/create-nft
touch src/pages/create-nft/CreateNft.tsx
```
Then move the file with styles from `assets` folder to the `create-nft` folder:
```
mv src/assets/styles/CreateNft.module.scss src/pages/create-nft
```
Let's start writing the `CreateNft.tsx`:
```typescript
import styles from 'CreateNft.module.scss'
export function CreateNft() {
return (
<div>Create NFT</div>
)
}
```
We should declare this page in the `index.tsx` and also add the route:
```typescript
import { Route, Routes } from 'react-router-dom';
import { CreateNft } from './create-nft/CreateNft';
import { Home } from './home/Home';
const routes = [
{ path: '/', Page: Home },
{ path: '/create-nft', Page: CreateNft },
];
export function Routing() {
const getRoutes = () => routes.map(({ path, Page }) =>
<Route key={path} path={path} element={<Page />} />
);
return <Routes>{getRoutes()}</Routes>;
}
```
Let's make a link to `CreateNft` page from the `Home` page. In the `Home.tsx` file let's write:
```typescript
import { Link } from "react-router-dom";
function Home() {
return (
<Link to="/create-nft">
<h3>Create NFT</h3>
</Link>
)
}
export { Home };
```
Now let's back to the `CreateNft` page. First we create the input form that include the NFT `title`, `description` and `image`:
```typescript
import { Button, FileInput, Input } from '@gear-js/ui'
import styles from './CreateNft.module.scss'
export function CreateNft() {
return (
<>
<h2 className={styles.heading}> Create NFT</h2>
<div className={styles.main}>
<form className={styles.from}>
<div className={styles.item}>
<Input label="Name" className={styles.input} required/>
</div>
<div className={styles.item}>
<Input label="Description" className={styles.input} required/>
</div>
<div className={styles.item}>
<FileInput label="Image" className={styles.input} required/>
</div>
<Button type="submit" text="Create" className={styles.button}/>
</form>
</div>
</>
)
}
```
Let's create state that will store NFT's title, description and image and add the function `handleInputChange` and `handleImageChange` that will store this state:
```typescript
import { Button, FileInput, Input } from '@gear-js/ui'
import { useState } from 'react'
import styles from './CreateNft.module.scss'
const NftInitialState = {
title: "",
description: "",
}
export function CreateNft() {
const [nftForm, setNftForm] = useState(NftInitialState);
const [image, setImage] = useState<File | null>(null)
const { title, description } = nftForm;
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setNftForm(prevForm => ({...prevForm , [name]: value}))
}
return (
<>
<h2 className={styles.heading}> Create NFT</h2>
<div className={styles.main}>
<form className={styles.from}>
<div className={styles.item}>
<Input label="Name" className={styles.input} required name="title" value={title} onChange={handleInputChange}/>
</div>
<div className={styles.item}>
<Input label="Description" className={styles.input} required name="description" value={description} onChange={handleInputChange}/>
</div>
<div className={styles.item}>
<FileInput label="Image" className={styles.input} onChange={setImage}/>
</div>
<Button type="submit" text="Create" className={styles.button}/>
</form>
</div>
</>
)
}
```
Let's also add the image preview for the uploaded image:
```typescript
...
export function CreateNft() {
...
return (
<>
<h2 className={styles.heading}> Create NFT</h2>
<div className={styles.main}>
<form className={styles.from}>
...
<div className={styles.item}>
<FileInput label="Image" className={styles.input} onChange={setImage}/>
{ image ? (
<div className="image-preview">
<img src={URL.createObjectURL(image)} alt="nft" style={{width: 100, height: 100}}/>
</div>
): (
<p>No image set for this NFT</p>
)}
</div>
<Button type="submit" text="Create" className={styles.button}/>
</form>
</div>
</>
)
}
```
Next we upload the image to the IPFS and send message `Mint` to the contract.
Install the [IPFS Desktop App](http://docs.ipfs.tech.ipns.localhost:8080/install/ipfs-desktop/#windows).
Go to `Settings`

Find `IPFS config`

and configure the `API` of your node:
```
"API": {
"HTTPHeaders": {
"Access-Control-Allow-Methods": [
"PUT",
"GET",
"POST"
],
"Access-Control-Allow-Origin": [
"*",
"https://webui.ipfs.io",
"http://webui.ipfs.io.ipns.localhost:8080",
"http://127.0.0.1:5001"
]
}
},
```
Now we are ready to upload the files from our application. Let's start writing the function.
```typescript
...
import { useIPFS } from 'hooks';
...
export function CreateNft() {
...
const ipfs = useIPFS();
const createNft = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
let cid;
if (image) {
try {
cid = await ipfs.add(image as File)
} catch (error) {
alert(error)
}
}
}
...
}
```
Next we should send the message to the contract. But before that let's write the necessary hooks.
Create file `api.ts` in the `hooks` folder:
```
touch src/hooks/api.ts
```
We'll define the hook `useNFTMetadata` and `useSendNFTMessage`:
```typescript
import { useSendMessage } from '@gear-js/react-hooks';
import metaTxt from 'assets/meta/meta.txt'
import { ADDRESS } from 'consts';
import { useMetadata } from "./useMetadata";
function useNFTMetadata() {
return useMetadata(metaTxt)
}
function useSendNFTMessage() {
const meta = useNFTMetadata()
return useSendMessage(ADDRESS.CONTRACT_ADDRESS, meta)
}
export {useNFTMetadata, useSendNFTMessage}
```
Let's continue writing the `CreateNft` function. We just create the message `payload` and send the message to the countract:
```typescript
...
import { useAccount } from '@gear-js/react-hooks';
import { useSendNFTMessage } from 'hooks/api';
import { useNavigate } from 'react-router-dom';
...
export function CreateNft() {
...
const ipfs = useIPFS();
const { account }= useAccount();
const navigate = useNavigate();
const handleMessage = useSendNFTMessage();
const resetForm = () => {
setNftForm(NftInitialState);
setImage(null)
}
const createNft = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
let cid;
if (image) {
try {
cid = await ipfs.add(image as File)
} catch (error) {
alert(error)
}
}
const tokenMetadata = {
name: title,
description,
media: cid?.cid.toString(),
reference: "",
}
const payload = {
Mint: {
to: account?.decodedAddress,
tokenMetadata,
}
};
handleMessage(
payload,
{
onSuccess: () => {
resetForm();
navigate('/')
},
},
);
}
...
}
```
The `CreateNft` page is ready. The whole code is:
```typescript
import { useAccount } from '@gear-js/react-hooks';
import { Button, FileInput, Input } from '@gear-js/ui'
import { useIPFS } from 'hooks';
import { useSendNFTMessage } from 'hooks/api';
import { useState } from 'react'
import { useNavigate } from 'react-router-dom';
import styles from './CreateNft.module.scss'
const NftInitialState = {
title: "",
description: "",
}
export function CreateNft() {
const [nftForm, setNftForm] = useState(NftInitialState);
const [image, setImage] = useState<File | null>(null)
const { title, description } = nftForm;
const handleInputChange = (e: {target: {name: any, value: any }}) => {
const { name, value } = e.target;
setNftForm({...nftForm, [name]: value})
}
const ipfs = useIPFS();
const { account }= useAccount();
const navigate = useNavigate();
const handleMessage = useSendNFTMessage();
const resetForm = () => {
setNftForm(NftInitialState);
setImage(null)
}
const createNft = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
let cid;
if (image) {
try {
cid = await ipfs.add(image as File)
} catch (error) {
alert(error)
}
}
const tokenMetadata = {
name: title,
description,
media: cid?.cid.toString(),
reference: "",
}
const payload = {
Mint: {
to: account?.decodedAddress,
tokenMetadata,
}
};
handleMessage(
payload,
{
onSuccess: () => {
resetForm();
navigate('/')
},
},
);
}
return (
<>
<h2 className={styles.heading}> Create NFT</h2>
<div className={styles.main}>
<form className={styles.from} onSubmit={createNft}>
<div className={styles.item}>
<Input label="Name" className={styles.input} required name="title" value={title} onChange={handleInputChange}/>
</div>
<div className={styles.item}>
<Input label="Description" className={styles.input} required name="description" value={description} onChange={handleInputChange}/>
</div>
<div className={styles.item}>
<FileInput label="Image" className={styles.input} onChange={setImage}/>
{ image ? (
<div className="image-preview">
<img src={URL.createObjectURL(image)} alt="nft" style={{width: 100, height: 100}}/>
</div>
): (
<p>No image set for this NFT</p>
)}
</div>
<Button type="submit" text="Create" className={styles.button}/>
</form>
</div>
</>
)
}
```
In the next section we'll create the `Home` page where we'll read and display the minted NFTs.
### Home page
In the `api.ts` file we'll add hooks for reading the contract state.
First let's add `useNFTState<T>`, where `T` is a type that we're expecting to read (for example `Token`). It'll accept the function name and payload if it's required for the indicated function:
```typescript
import stateMetaWasm from 'assets/wasm/nft_state.meta.wasm'
import { useMetadata, useWasmMetadata } from './useMetadata'
import metaTxt from 'assets/meta/meta.txt'
import { useAccount, useReadWasmState, useSendMessage } from '@gear-js/react-hooks';
import { ADDRESS } from 'consts';
function useNFTMetadata() {
return useMetadata(metaTxt);
}
function useNFTState<T>(functionName: string, payload?: any) {
const { buffer } = useWasmMetadata(stateMetaWasm);
return useReadWasmState<T>(
ADDRESS.CONTRACT_ADDRESS,
buffer,
functionName,
payload
)
}
```
Let's read all the tokens our contract has. At first, we'll create the type for token in the separate folder `types`:
```
mkdir types
touch types/index.ts
```
and add to the `intex.ts` file the `Token` description:
```typescript
import { HexString } from "@polkadot/util/types";
type Token = {
approvedAccountIds: HexString[];
description: string;
id: string;
media: string;
name: string;
ownerId: HexString;
reference: string;
};
export type { Token };
```
Then we can write `useNFTs` hook:
```typescript
...
import { Token } from 'types';
...
function useNFTs() {
const { state } = useNFTState<Token[]>("all_tokens", null);
return state;
}
```
Now let's start writing the `Home` page.
```typescript
import { Loader } from 'components';
import { useNFTs } from 'hooks/api';
import styles from './Home.module.scss'
function Home() {
const nfts = useNFTs();
const isAnyNft = !!nfts?.length;
return (
<>
<header className={styles.header}>
<h2 className={styles.heading}>NFTs</h2>
</header>
{nfts ? (
<>
{isAnyNft && <ul className={styles.list}>Display NFTs here</ul>}
{!isAnyNft && <h2>There are no NFTs at the moment</h2>}
</>
) : (
<Loader />
)}
</>
)
}
export { Home };
```
We read `nfts` using the previously written hook `useNFTs`.
```typescript
const nfts = useNFTs();
```
Then we check whether the contract has tokens:
```typescript
const isAnyNft = !!nfts?.length;
```
Let's create a component that will display NFT:
```
mkdir pages/home/nft
touch pages/home/nft/nft.tsx
```
and write the component:
```typescript
import { Link } from "react-router-dom";
import { getIpfsAddress } from "utils";
import styles from './nft.module.scss'
type Props = {
id: string;
name: string;
media: string
}
function NFT( {id, name, media }: Props) {
const to = `/nft/${id}`;
const src = getIpfsAddress(media)
const text = `#${id}`
return (
<Link to={to} className={styles.nft}>
<img src={src} alt={name}/>
<h3 className={styles.heading}>{name}</h3>
<p className={styles.text}>{text}</p>
</Link>
)
}
export { NFT };
```
Then let's write a function for getting all NFTs from the contract in the `Home.tsx`:
```typescript
...
import { NFT } from './nft/nft';
function Home() {
const nfts = useNFTs();
const isAnyNft = nfts?.length;
const getNFTs = () =>
nfts?.map( ({name, id, media}) => (
<li key={id}>
<NFT id = {id} name = {name} media = {media} />
</li>
))
...
}
export { Home };
```
The whole code of the `Home` page:
```typescript
import { Loader } from 'components';
import { useNFTs } from 'hooks/api';
import styles from './Home.module.scss'
import { NFT } from './nft/nft';
function Home() {
const nfts = useNFTs();
const isAnyNft = nfts?.length;
const getNFTs = () =>
nfts?.map( ({name, id, media}) => (
<li key={id}>
<NFT id = {id} name = {name} media = {media} />
</li>
))
const NFTs = getNFTs();
return (
<>
<header className={styles.header}>
<h2 className={styles.heading}>NFTs</h2>
</header>
{nfts ? (
<>
{isAnyNft && <ul className={styles.list}>{NFTs}</ul>}
{!isAnyNft && <h2>There are no NFTs at the moment</h2>}
</>
) : (
<Loader />
)}
</>
)
}
export { Home };
```