---
# System prepended metadata

title: NFT frontend application

---

# 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`
![](https://i.imgur.com/ssgQSvY.jpg)
Find `IPFS config`

![](https://i.imgur.com/nn82YO3.png)

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 };
```