Icon LinkBuilding the Frontend

Icon LinkSetup

Initialize a new React app with TypeScript in the same parent folder as your contract using the command below.

npx create-react-app frontend --template typescript

Next, install the fuels Typescript and wallet SDKs in the frontend folder and generate types from your contract with fuels typegen.

cd frontend
npm install fuels @fuel-wallet/sdk
npx fuels typegen -i ../contract/out/debug/*-abi.json -o ./src/contracts

In the tsconfig.json file, add the line below in the compilerOptions object to add the Fuel wallet type on the window object.

"types": ["@fuel-wallet/sdk"],

Open the src/App.tsx file, and replace the boilerplate code with the template below:

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Finally, copy and paste the CSS code below in your App.css file to add some simple styling.

.App {
  text-align: center;
}
 
nav > ul {
  list-style-type: none;
  display: flex;
  justify-content: center;
  gap: 1rem;
  padding-inline-start: 0;
}
 
nav > ul > li {
  cursor: pointer;
}
 
.form-control{
  text-align: left;
  font-size: 18px;
  display: flex;
  flex-direction: column;
  margin: 0 auto;
  max-width: 400px;
}
 
.form-control > input {
  margin-bottom: 1rem;
}
 
.form-control > button {
  cursor: pointer;
  background: #054a9f;
  color: white;
  border: none;
  border-radius: 8px;
  padding: 10px 0;
  font-size: 20px;
}
 
.items-container{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem;
  margin: 1rem 0;
}
 
.item-card{
  box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.2);
  border-radius: 8px;
  max-width: 300px;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
 
.active-tab{
  border-bottom: 4px solid #77b6d8;
}
 
button {
  cursor: pointer;
  background: #054a9f;
  border: none;
  border-radius: 12px;
  padding: 10px 20px;
  margin-top: 20px;
  font-size: 20px;
  color: white;
}

Icon LinkConnecting to the contract

At the top of the file, add your contract ID as a constant.

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Next, create a new folder in the src folder called hooks, and copy and paste the useFuel.tsx and useIsConnected.tsx hooks from the example in this repo. You can also find them in the offical wallet docs Icon Link.

In App.tsx, import these hooks.

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

In the App function, we can call these hooks like this:

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Now we can check if the user has the fuel wallet installed and check if it's connected.

If the user doesn't have the fuel object in their window, we know that they don't have the Fuel wallet extention installed. If they have it installed, we can then check if the wallet is connected.

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Next, let's add a state variable called wallets with the useState hook, which will have the type WalletLocked.

You can think of a locked wallet as a user wallet you can't sign transactions for, and an unlocked wallet as a wallet where you have the private key and are able to sign transactions.

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Next, we can use the useMemo hook to connect to our contract with the connected wallet.

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Now we have our contract connection ready. You can console log the contract here to make sure this is working correctly.

Icon LinkUI

In our app we're going to have two tabs: one to see all of the items listed for sale, and one to list a new item for sale.

Let's add another state variable called active that we can use to toggle between our tabs. We can set the default tab to show all listed items.

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Below the header, add a nav section to toggle between the two options.

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Next we can create our components to show and list items.

Icon LinkListing an Item

Create a new folder in the src folder called components, and create a file there component called ListItem.tsx.

At the top of the file, import the useState hook from react, the generated contract ABI from the contracts folder, and bn (big number) type from fuels.

import { useState } from "react";
import { ContractAbi } from "../contracts";
import { bn } from "fuels";
 
interface ListItemsProps {
  contract: ContractAbi | null;
}
 
export default function ListItem({contract}: ListItemsProps){
    const [metadata, setMetadata] = useState<string>("");
    const [price, setPrice] = useState<string>("0");
    const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>){
        e.preventDefault();
        setStatus('loading')
        if(contract !== null){
            try {
                const priceInput = bn.parseUnits(price.toString());
                await contract.functions.list_item(priceInput, metadata).call();
                setStatus('success')
            } catch (e) {
                console.log("ERROR:", e);
                setStatus('error')
            }
        } else {
            console.log("ERROR: Contract is null");
        }
    }
    
    return (
        <div>
            <h2>List an Item</h2>
            {status === 'none' &&
            <form onSubmit={handleSubmit}>
                <div className="form-control">
                    <label htmlFor="metadata">Item Metadata:</label>
                    <input 
                        id="metadata" 
                        type="text" 
                        pattern="\w{20}" 
                        title="The metatdata must be 20 characters"
                        required 
                        onChange={(e) => setMetadata(e.target.value)}
                    />
                </div>
 
                <div className="form-control">
                    <label htmlFor="price">Item Price:</label>
                    <input
                        id="price"
                        type="number"
                        required
                        min="0"
                        step="any"
                        inputMode="decimal"
                        placeholder="0.00"
                        onChange={(e) => {
                          setPrice(e.target.value);
                        }}
                      />
                </div>
 
                <div className="form-control">
                    <button type="submit">List item</button>
                </div>
            </form>
            }
 
            {status === 'success' && <div>Item successfully listed!</div>}
            {status === 'error' && <div>Error listing item. Please try again.</div>}
            {status === 'loading' && <div>Listing item...</div>}
        </div>
    )
}

This component will take the contract we made in App.tsx as a prop, so let's create an interface for the component.

import { useState } from "react";
import { ContractAbi } from "../contracts";
import { bn } from "fuels";
 
interface ListItemsProps {
  contract: ContractAbi | null;
}
 
export default function ListItem({contract}: ListItemsProps){
    const [metadata, setMetadata] = useState<string>("");
    const [price, setPrice] = useState<string>("0");
    const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>){
        e.preventDefault();
        setStatus('loading')
        if(contract !== null){
            try {
                const priceInput = bn.parseUnits(price.toString());
                await contract.functions.list_item(priceInput, metadata).call();
                setStatus('success')
            } catch (e) {
                console.log("ERROR:", e);
                setStatus('error')
            }
        } else {
            console.log("ERROR: Contract is null");
        }
    }
    
    return (
        <div>
            <h2>List an Item</h2>
            {status === 'none' &&
            <form onSubmit={handleSubmit}>
                <div className="form-control">
                    <label htmlFor="metadata">Item Metadata:</label>
                    <input 
                        id="metadata" 
                        type="text" 
                        pattern="\w{20}" 
                        title="The metatdata must be 20 characters"
                        required 
                        onChange={(e) => setMetadata(e.target.value)}
                    />
                </div>
 
                <div className="form-control">
                    <label htmlFor="price">Item Price:</label>
                    <input
                        id="price"
                        type="number"
                        required
                        min="0"
                        step="any"
                        inputMode="decimal"
                        placeholder="0.00"
                        onChange={(e) => {
                          setPrice(e.target.value);
                        }}
                      />
                </div>
 
                <div className="form-control">
                    <button type="submit">List item</button>
                </div>
            </form>
            }
 
            {status === 'success' && <div>Item successfully listed!</div>}
            {status === 'error' && <div>Error listing item. Please try again.</div>}
            {status === 'loading' && <div>Listing item...</div>}
        </div>
    )
}

We can set up the template for the function like this.

import { useState } from "react";
import { ContractAbi } from "../contracts";
import { bn } from "fuels";
 
interface ListItemsProps {
  contract: ContractAbi | null;
}
 
export default function ListItem({contract}: ListItemsProps){
    const [metadata, setMetadata] = useState<string>("");
    const [price, setPrice] = useState<string>("0");
    const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>){
        e.preventDefault();
        setStatus('loading')
        if(contract !== null){
            try {
                const priceInput = bn.parseUnits(price.toString());
                await contract.functions.list_item(priceInput, metadata).call();
                setStatus('success')
            } catch (e) {
                console.log("ERROR:", e);
                setStatus('error')
            }
        } else {
            console.log("ERROR: Contract is null");
        }
    }
    
    return (
        <div>
            <h2>List an Item</h2>
            {status === 'none' &&
            <form onSubmit={handleSubmit}>
                <div className="form-control">
                    <label htmlFor="metadata">Item Metadata:</label>
                    <input 
                        id="metadata" 
                        type="text" 
                        pattern="\w{20}" 
                        title="The metatdata must be 20 characters"
                        required 
                        onChange={(e) => setMetadata(e.target.value)}
                    />
                </div>
 
                <div className="form-control">
                    <label htmlFor="price">Item Price:</label>
                    <input
                        id="price"
                        type="number"
                        required
                        min="0"
                        step="any"
                        inputMode="decimal"
                        placeholder="0.00"
                        onChange={(e) => {
                          setPrice(e.target.value);
                        }}
                      />
                </div>
 
                <div className="form-control">
                    <button type="submit">List item</button>
                </div>
            </form>
            }
 
            {status === 'success' && <div>Item successfully listed!</div>}
            {status === 'error' && <div>Error listing item. Please try again.</div>}
            {status === 'loading' && <div>Listing item...</div>}
        </div>
    )
}

To list an item, we'll create a form where the user can input the metadata string and price for the item they want to list. Let's start by adding some state variables for the metadata and price. We can also add a status variable to track the submit status.

import { useState } from "react";
import { ContractAbi } from "../contracts";
import { bn } from "fuels";
 
interface ListItemsProps {
  contract: ContractAbi | null;
}
 
export default function ListItem({contract}: ListItemsProps){
    const [metadata, setMetadata] = useState<string>("");
    const [price, setPrice] = useState<string>("0");
    const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>){
        e.preventDefault();
        setStatus('loading')
        if(contract !== null){
            try {
                const priceInput = bn.parseUnits(price.toString());
                await contract.functions.list_item(priceInput, metadata).call();
                setStatus('success')
            } catch (e) {
                console.log("ERROR:", e);
                setStatus('error')
            }
        } else {
            console.log("ERROR: Contract is null");
        }
    }
    
    return (
        <div>
            <h2>List an Item</h2>
            {status === 'none' &&
            <form onSubmit={handleSubmit}>
                <div className="form-control">
                    <label htmlFor="metadata">Item Metadata:</label>
                    <input 
                        id="metadata" 
                        type="text" 
                        pattern="\w{20}" 
                        title="The metatdata must be 20 characters"
                        required 
                        onChange={(e) => setMetadata(e.target.value)}
                    />
                </div>
 
                <div className="form-control">
                    <label htmlFor="price">Item Price:</label>
                    <input
                        id="price"
                        type="number"
                        required
                        min="0"
                        step="any"
                        inputMode="decimal"
                        placeholder="0.00"
                        onChange={(e) => {
                          setPrice(e.target.value);
                        }}
                      />
                </div>
 
                <div className="form-control">
                    <button type="submit">List item</button>
                </div>
            </form>
            }
 
            {status === 'success' && <div>Item successfully listed!</div>}
            {status === 'error' && <div>Error listing item. Please try again.</div>}
            {status === 'loading' && <div>Listing item...</div>}
        </div>
    )
}

Under the heading, add the code below for the form:

import { useState } from "react";
import { ContractAbi } from "../contracts";
import { bn } from "fuels";
 
interface ListItemsProps {
  contract: ContractAbi | null;
}
 
export default function ListItem({contract}: ListItemsProps){
    const [metadata, setMetadata] = useState<string>("");
    const [price, setPrice] = useState<string>("0");
    const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>){
        e.preventDefault();
        setStatus('loading')
        if(contract !== null){
            try {
                const priceInput = bn.parseUnits(price.toString());
                await contract.functions.list_item(priceInput, metadata).call();
                setStatus('success')
            } catch (e) {
                console.log("ERROR:", e);
                setStatus('error')
            }
        } else {
            console.log("ERROR: Contract is null");
        }
    }
    
    return (
        <div>
            <h2>List an Item</h2>
            {status === 'none' &&
            <form onSubmit={handleSubmit}>
                <div className="form-control">
                    <label htmlFor="metadata">Item Metadata:</label>
                    <input 
                        id="metadata" 
                        type="text" 
                        pattern="\w{20}" 
                        title="The metatdata must be 20 characters"
                        required 
                        onChange={(e) => setMetadata(e.target.value)}
                    />
                </div>
 
                <div className="form-control">
                    <label htmlFor="price">Item Price:</label>
                    <input
                        id="price"
                        type="number"
                        required
                        min="0"
                        step="any"
                        inputMode="decimal"
                        placeholder="0.00"
                        onChange={(e) => {
                          setPrice(e.target.value);
                        }}
                      />
                </div>
 
                <div className="form-control">
                    <button type="submit">List item</button>
                </div>
            </form>
            }
 
            {status === 'success' && <div>Item successfully listed!</div>}
            {status === 'error' && <div>Error listing item. Please try again.</div>}
            {status === 'loading' && <div>Listing item...</div>}
        </div>
    )
}

Finally, we need to add the handleSubmit function. We can use the contract prop to call the list_item function and pass in the price and metadata from the form.

import { useState } from "react";
import { ContractAbi } from "../contracts";
import { bn } from "fuels";
 
interface ListItemsProps {
  contract: ContractAbi | null;
}
 
export default function ListItem({contract}: ListItemsProps){
    const [metadata, setMetadata] = useState<string>("");
    const [price, setPrice] = useState<string>("0");
    const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>){
        e.preventDefault();
        setStatus('loading')
        if(contract !== null){
            try {
                const priceInput = bn.parseUnits(price.toString());
                await contract.functions.list_item(priceInput, metadata).call();
                setStatus('success')
            } catch (e) {
                console.log("ERROR:", e);
                setStatus('error')
            }
        } else {
            console.log("ERROR: Contract is null");
        }
    }
    
    return (
        <div>
            <h2>List an Item</h2>
            {status === 'none' &&
            <form onSubmit={handleSubmit}>
                <div className="form-control">
                    <label htmlFor="metadata">Item Metadata:</label>
                    <input 
                        id="metadata" 
                        type="text" 
                        pattern="\w{20}" 
                        title="The metatdata must be 20 characters"
                        required 
                        onChange={(e) => setMetadata(e.target.value)}
                    />
                </div>
 
                <div className="form-control">
                    <label htmlFor="price">Item Price:</label>
                    <input
                        id="price"
                        type="number"
                        required
                        min="0"
                        step="any"
                        inputMode="decimal"
                        placeholder="0.00"
                        onChange={(e) => {
                          setPrice(e.target.value);
                        }}
                      />
                </div>
 
                <div className="form-control">
                    <button type="submit">List item</button>
                </div>
            </form>
            }
 
            {status === 'success' && <div>Item successfully listed!</div>}
            {status === 'error' && <div>Error listing item. Please try again.</div>}
            {status === 'loading' && <div>Listing item...</div>}
        </div>
    )
}

Now that we have this component, let's add it to our App.tsx file and try it out.

Import the ListItem component at the top of the file. Then, replace where it says Connected! with the code below:

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

Now, try listing an item to make sure this works. You should see the message Item successfully listed!.

Icon LinkShow All Items

Next, let's create a new file called AllItems.tsx in the components folder.

Copy and paste the template code below for this component:

import { useState, useEffect } from "react";
import { ContractAbi } from "../contracts";
import { ItemOutput } from "../contracts/ContractAbi";
import ItemCard from "./ItemCard";
 
interface AllItemsProps {
  contract: ContractAbi | null;
}
 
export default function AllItems({ contract }: AllItemsProps) {
  const [items, setItems] = useState<ItemOutput[]>([]);
  const [itemCount, setItemCount] = useState<number>(0);
  const [status, setStatus] = useState<'success' | 'loading' | 'error'>('loading');
 
  useEffect(() => {
    async function getAllItems() {
      if (contract !== null) {
        try {
          let { value } = await contract.functions.get_count().simulate();
          let formattedValue = parseFloat(value.format()) * 1_000_000_000;
          setItemCount(formattedValue);
          let max = formattedValue + 1;
          let tempItems = [];
          for(let i=1; i < max; i++){
            let resp = await contract.functions.get_item(i).simulate();
            tempItems.push(resp.value)
          }
          setItems(tempItems)
          setStatus('success')
        } catch (e) {
          setStatus('error')
          console.log("ERROR:", e);
        }
      }
    }
    getAllItems();
  }, [contract]);
 
  return (
    <div>
      <h2>All Items</h2>
      {status === 'success' &&
        <div>
          {itemCount === 0 ? (
            <div>Uh oh! No items have been listed yet</div>
          ) : (
            <div>
              <div>Total items: {itemCount}</div>
              <div className="items-container">
                  {items.map((item) => (
                  <ItemCard key={item.id.format()} contract={contract} item={item}/>
              ))}
              </div>
          </div>
          )}
        </div>
      }
      {status === 'error' && <div>Something went wrong, try reloading the page.</div>}
      {status === 'loading' && <div>Loading...</div>}
    </div>
  );
}

Here we can get the item count to see how many items are listed, and then loop through each of them to get the item details.

First, let's create some state variables to store the number of items listed, an array of the item details, and the loading status.

import { useState, useEffect } from "react";
import { ContractAbi } from "../contracts";
import { ItemOutput } from "../contracts/ContractAbi";
import ItemCard from "./ItemCard";
 
interface AllItemsProps {
  contract: ContractAbi | null;
}
 
export default function AllItems({ contract }: AllItemsProps) {
  const [items, setItems] = useState<ItemOutput[]>([]);
  const [itemCount, setItemCount] = useState<number>(0);
  const [status, setStatus] = useState<'success' | 'loading' | 'error'>('loading');
 
  useEffect(() => {
    async function getAllItems() {
      if (contract !== null) {
        try {
          let { value } = await contract.functions.get_count().simulate();
          let formattedValue = parseFloat(value.format()) * 1_000_000_000;
          setItemCount(formattedValue);
          let max = formattedValue + 1;
          let tempItems = [];
          for(let i=1; i < max; i++){
            let resp = await contract.functions.get_item(i).simulate();
            tempItems.push(resp.value)
          }
          setItems(tempItems)
          setStatus('success')
        } catch (e) {
          setStatus('error')
          console.log("ERROR:", e);
        }
      }
    }
    getAllItems();
  }, [contract]);
 
  return (
    <div>
      <h2>All Items</h2>
      {status === 'success' &&
        <div>
          {itemCount === 0 ? (
            <div>Uh oh! No items have been listed yet</div>
          ) : (
            <div>
              <div>Total items: {itemCount}</div>
              <div className="items-container">
                  {items.map((item) => (
                  <ItemCard key={item.id.format()} contract={contract} item={item}/>
              ))}
              </div>
          </div>
          )}
        </div>
      }
      {status === 'error' && <div>Something went wrong, try reloading the page.</div>}
      {status === 'loading' && <div>Loading...</div>}
    </div>
  );
}

Next, let's fetch the items in a useEffect hook. Because these are read-only functions, we can simulate a dry-run of the transaction by using the get method instead of call so the user doesn't have to sign anything.

import { useState, useEffect } from "react";
import { ContractAbi } from "../contracts";
import { ItemOutput } from "../contracts/ContractAbi";
import ItemCard from "./ItemCard";
 
interface AllItemsProps {
  contract: ContractAbi | null;
}
 
export default function AllItems({ contract }: AllItemsProps) {
  const [items, setItems] = useState<ItemOutput[]>([]);
  const [itemCount, setItemCount] = useState<number>(0);
  const [status, setStatus] = useState<'success' | 'loading' | 'error'>('loading');
 
  useEffect(() => {
    async function getAllItems() {
      if (contract !== null) {
        try {
          let { value } = await contract.functions.get_count().simulate();
          let formattedValue = parseFloat(value.format()) * 1_000_000_000;
          setItemCount(formattedValue);
          let max = formattedValue + 1;
          let tempItems = [];
          for(let i=1; i < max; i++){
            let resp = await contract.functions.get_item(i).simulate();
            tempItems.push(resp.value)
          }
          setItems(tempItems)
          setStatus('success')
        } catch (e) {
          setStatus('error')
          console.log("ERROR:", e);
        }
      }
    }
    getAllItems();
  }, [contract]);
 
  return (
    <div>
      <h2>All Items</h2>
      {status === 'success' &&
        <div>
          {itemCount === 0 ? (
            <div>Uh oh! No items have been listed yet</div>
          ) : (
            <div>
              <div>Total items: {itemCount}</div>
              <div className="items-container">
                  {items.map((item) => (
                  <ItemCard key={item.id.format()} contract={contract} item={item}/>
              ))}
              </div>
          </div>
          )}
        </div>
      }
      {status === 'error' && <div>Something went wrong, try reloading the page.</div>}
      {status === 'loading' && <div>Loading...</div>}
    </div>
  );
}

If the item count is greater than 0 and we are able to successfully load the items, we can map through them and display an item card.

The item card will show the item details and a buy button to buy that item, so we'll need to pass the contract and the item as props.

import { useState, useEffect } from "react";
import { ContractAbi } from "../contracts";
import { ItemOutput } from "../contracts/ContractAbi";
import ItemCard from "./ItemCard";
 
interface AllItemsProps {
  contract: ContractAbi | null;
}
 
export default function AllItems({ contract }: AllItemsProps) {
  const [items, setItems] = useState<ItemOutput[]>([]);
  const [itemCount, setItemCount] = useState<number>(0);
  const [status, setStatus] = useState<'success' | 'loading' | 'error'>('loading');
 
  useEffect(() => {
    async function getAllItems() {
      if (contract !== null) {
        try {
          let { value } = await contract.functions.get_count().simulate();
          let formattedValue = parseFloat(value.format()) * 1_000_000_000;
          setItemCount(formattedValue);
          let max = formattedValue + 1;
          let tempItems = [];
          for(let i=1; i < max; i++){
            let resp = await contract.functions.get_item(i).simulate();
            tempItems.push(resp.value)
          }
          setItems(tempItems)
          setStatus('success')
        } catch (e) {
          setStatus('error')
          console.log("ERROR:", e);
        }
      }
    }
    getAllItems();
  }, [contract]);
 
  return (
    <div>
      <h2>All Items</h2>
      {status === 'success' &&
        <div>
          {itemCount === 0 ? (
            <div>Uh oh! No items have been listed yet</div>
          ) : (
            <div>
              <div>Total items: {itemCount}</div>
              <div className="items-container">
                  {items.map((item) => (
                  <ItemCard key={item.id.format()} contract={contract} item={item}/>
              ))}
              </div>
          </div>
          )}
        </div>
      }
      {status === 'error' && <div>Something went wrong, try reloading the page.</div>}
      {status === 'loading' && <div>Loading...</div>}
    </div>
  );
}

Icon LinkItem Card

Now let's create the item card component. Create a new file called ItemCard.tsx in the components folder, and copy and paste the template code below.

import { useState } from "react";
import { ItemOutput } from "../contracts/ContractAbi";
import { ContractAbi } from "../contracts";
 
interface ItemCardProps {
  contract: ContractAbi | null;
  item: ItemOutput;
}
 
const assetId = "0x0000000000000000000000000000000000000000000000000000000000000000"
 
export default function ItemCard({ item, contract }: ItemCardProps) {
  const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
  async function handleBuyItem() {
    if (contract !== null) {
      setStatus('loading')
      try {
        await contract.functions.buy_item(item.id)
        .txParams({ variableOutputs: 1 })
        .callParams({
            forward: [item.price, assetId],
          })
        .call()
        setStatus("success");
      } catch (e) {
        console.log("ERROR:", e);
      }
    }
  }
 
  return (
    <div className="item-card">
      <div>Id: {parseFloat(item.id.format()) * 1_000_000_000}</div>
      <div>Metadata: {item.metadata}</div>
      <div>Price: {parseFloat(item.price.format())} ETH</div>
      <div>Total Bought: {parseFloat(item.total_bought.format()) * 1_000_000_000}</div>
      {status === 'success' && <div>Purchased ✅</div>}
      {status === 'error' && <div>Something went wrong ❌</div>}
      {status === 'none' &&  <button onClick={handleBuyItem}>Buy Item</button>}
      {status === 'loading' && <div>Buying item..</div>}
    
    </div>
  );
}

Add a status variable to track the status of the buy button.

import { useState } from "react";
import { ItemOutput } from "../contracts/ContractAbi";
import { ContractAbi } from "../contracts";
 
interface ItemCardProps {
  contract: ContractAbi | null;
  item: ItemOutput;
}
 
const assetId = "0x0000000000000000000000000000000000000000000000000000000000000000"
 
export default function ItemCard({ item, contract }: ItemCardProps) {
  const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
  async function handleBuyItem() {
    if (contract !== null) {
      setStatus('loading')
      try {
        await contract.functions.buy_item(item.id)
        .txParams({ variableOutputs: 1 })
        .callParams({
            forward: [item.price, assetId],
          })
        .call()
        setStatus("success");
      } catch (e) {
        console.log("ERROR:", e);
      }
    }
  }
 
  return (
    <div className="item-card">
      <div>Id: {parseFloat(item.id.format()) * 1_000_000_000}</div>
      <div>Metadata: {item.metadata}</div>
      <div>Price: {parseFloat(item.price.format())} ETH</div>
      <div>Total Bought: {parseFloat(item.total_bought.format()) * 1_000_000_000}</div>
      {status === 'success' && <div>Purchased ✅</div>}
      {status === 'error' && <div>Something went wrong ❌</div>}
      {status === 'none' &&  <button onClick={handleBuyItem}>Buy Item</button>}
      {status === 'loading' && <div>Buying item..</div>}
    
    </div>
  );
}

Then add the item details and status messages to the card.

import { useState } from "react";
import { ItemOutput } from "../contracts/ContractAbi";
import { ContractAbi } from "../contracts";
 
interface ItemCardProps {
  contract: ContractAbi | null;
  item: ItemOutput;
}
 
const assetId = "0x0000000000000000000000000000000000000000000000000000000000000000"
 
export default function ItemCard({ item, contract }: ItemCardProps) {
  const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
  async function handleBuyItem() {
    if (contract !== null) {
      setStatus('loading')
      try {
        await contract.functions.buy_item(item.id)
        .txParams({ variableOutputs: 1 })
        .callParams({
            forward: [item.price, assetId],
          })
        .call()
        setStatus("success");
      } catch (e) {
        console.log("ERROR:", e);
      }
    }
  }
 
  return (
    <div className="item-card">
      <div>Id: {parseFloat(item.id.format()) * 1_000_000_000}</div>
      <div>Metadata: {item.metadata}</div>
      <div>Price: {parseFloat(item.price.format())} ETH</div>
      <div>Total Bought: {parseFloat(item.total_bought.format()) * 1_000_000_000}</div>
      {status === 'success' && <div>Purchased ✅</div>}
      {status === 'error' && <div>Something went wrong ❌</div>}
      {status === 'none' &&  <button onClick={handleBuyItem}>Buy Item</button>}
      {status === 'loading' && <div>Buying item..</div>}
    
    </div>
  );
}

Create a new async function called handleBuyItem. Because this function is payable and transfers coins to the item owner, we'll need to do a couple special things here.

Whenever we call any function that uses the transfer or mint functions in Sway, we have to append the matching number of variable outputs to the call with the txParams method. Because the buy_item function just transfers assets to the item owner, the number of variable outputs is 1.

Next, because this function is payable and the user needs to transfer the price of the item, we'll use the callParams method to forward the amount. With Fuel you can transfer any type of asset, so we need to specify both the amount and the asset ID.

import { useState } from "react";
import { ItemOutput } from "../contracts/ContractAbi";
import { ContractAbi } from "../contracts";
 
interface ItemCardProps {
  contract: ContractAbi | null;
  item: ItemOutput;
}
 
const assetId = "0x0000000000000000000000000000000000000000000000000000000000000000"
 
export default function ItemCard({ item, contract }: ItemCardProps) {
  const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
  async function handleBuyItem() {
    if (contract !== null) {
      setStatus('loading')
      try {
        await contract.functions.buy_item(item.id)
        .txParams({ variableOutputs: 1 })
        .callParams({
            forward: [item.price, assetId],
          })
        .call()
        setStatus("success");
      } catch (e) {
        console.log("ERROR:", e);
      }
    }
  }
 
  return (
    <div className="item-card">
      <div>Id: {parseFloat(item.id.format()) * 1_000_000_000}</div>
      <div>Metadata: {item.metadata}</div>
      <div>Price: {parseFloat(item.price.format())} ETH</div>
      <div>Total Bought: {parseFloat(item.total_bought.format()) * 1_000_000_000}</div>
      {status === 'success' && <div>Purchased ✅</div>}
      {status === 'error' && <div>Something went wrong ❌</div>}
      {status === 'none' &&  <button onClick={handleBuyItem}>Buy Item</button>}
      {status === 'loading' && <div>Buying item..</div>}
    
    </div>
  );
}

Go back to AllItems.tsx and import the ItemCard component we just made.

Now you should be able to see and buy all of the items listed in your contract.

Icon LinkCheckpoint

Ensure that all your files are correctly configured by examining the code below. If you require additional assistance, refer to the repository here Icon Link

App.tsx

import { useState, useMemo } from "react";
import { useFuel, useIsConnected, useAccount, useWallet } from '@fuel-wallet/react';
import { ContractAbi__factory } from "./contracts"
import AllItems from "./components/AllItems";
import ListItem from "./components/ListItem";
import "./App.css";
 
const CONTRACT_ID = "0xe924cde59c8b07fe4155f484038cdab8a027e3549eda80022c7c515a4933a594"
 
function App() {
  const [active, setActive] = useState<'all-items' | 'list-item'>('all-items');
  const fuelObj = useFuel();
  const isConnectedObj = useIsConnected();
  const accountObj = useAccount();
  const walletObj = useWallet({ address: accountObj.account });
 
  const contract = useMemo(() => {
    if (walletObj.wallet) {
      const contract = ContractAbi__factory.connect(CONTRACT_ID, walletObj.wallet);
      return contract;
    }
    return null;
  }, [walletObj]);
 
  return (
    <div className="App">
      <header>
        <h1>Sway Marketplace</h1>
      </header>
      <nav>
        <ul>
          <li 
          className={active === 'all-items' ? "active-tab" : ""} 
          onClick={() => setActive('all-items')}
          >
            See All Items
          </li>
          <li 
          className={active === 'list-item' ? "active-tab" : ""} 
          onClick={() => setActive('list-item')}
          >
            List an Item
          </li>
        </ul>
      </nav>
 
      {fuelObj.fuel ? (
        <div>
          { isConnectedObj.isConnected ? (
            <div>
              {active === 'all-items' && <AllItems contract={contract} />}
              {active === 'list-item' && <ListItem contract={contract} />}
            </div>
          ) : (
            <div>
              <button onClick={() => fuelObj.fuel?.connect()}>
                Connect Wallet
              </button>
          </div>
          )}
        </div>
      ) : (
        <div>
          Download the{" "}
          <a
            target="_blank"
            rel="noopener noreferrer"
            href="https://wallet.fuel.network/"
          >
            Fuel Wallet
          </a>{" "}
          to use the app.
        </div>
      )}
    </div>
  );
}
 
export default App;

AllItems.tsx

import { useState, useEffect } from "react";
import { ContractAbi } from "../contracts";
import { ItemOutput } from "../contracts/ContractAbi";
import ItemCard from "./ItemCard";
 
interface AllItemsProps {
  contract: ContractAbi | null;
}
 
export default function AllItems({ contract }: AllItemsProps) {
  const [items, setItems] = useState<ItemOutput[]>([]);
  const [itemCount, setItemCount] = useState<number>(0);
  const [status, setStatus] = useState<'success' | 'loading' | 'error'>('loading');
 
  useEffect(() => {
    async function getAllItems() {
      if (contract !== null) {
        try {
          let { value } = await contract.functions.get_count().simulate();
          let formattedValue = parseFloat(value.format()) * 1_000_000_000;
          setItemCount(formattedValue);
          let max = formattedValue + 1;
          let tempItems = [];
          for(let i=1; i < max; i++){
            let resp = await contract.functions.get_item(i).simulate();
            tempItems.push(resp.value)
          }
          setItems(tempItems)
          setStatus('success')
        } catch (e) {
          setStatus('error')
          console.log("ERROR:", e);
        }
      }
    }
    getAllItems();
  }, [contract]);
 
  return (
    <div>
      <h2>All Items</h2>
      {status === 'success' &&
        <div>
          {itemCount === 0 ? (
            <div>Uh oh! No items have been listed yet</div>
          ) : (
            <div>
              <div>Total items: {itemCount}</div>
              <div className="items-container">
                  {items.map((item) => (
                  <ItemCard key={item.id.format()} contract={contract} item={item}/>
              ))}
              </div>
          </div>
          )}
        </div>
      }
      {status === 'error' && <div>Something went wrong, try reloading the page.</div>}
      {status === 'loading' && <div>Loading...</div>}
    </div>
  );
}

ItemCard.tsx

import { useState } from "react";
import { ItemOutput } from "../contracts/ContractAbi";
import { ContractAbi } from "../contracts";
 
interface ItemCardProps {
  contract: ContractAbi | null;
  item: ItemOutput;
}
 
const assetId = "0x0000000000000000000000000000000000000000000000000000000000000000"
 
export default function ItemCard({ item, contract }: ItemCardProps) {
  const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
  async function handleBuyItem() {
    if (contract !== null) {
      setStatus('loading')
      try {
        await contract.functions.buy_item(item.id)
        .txParams({ variableOutputs: 1 })
        .callParams({
            forward: [item.price, assetId],
          })
        .call()
        setStatus("success");
      } catch (e) {
        console.log("ERROR:", e);
      }
    }
  }
 
  return (
    <div className="item-card">
      <div>Id: {parseFloat(item.id.format()) * 1_000_000_000}</div>
      <div>Metadata: {item.metadata}</div>
      <div>Price: {parseFloat(item.price.format())} ETH</div>
      <div>Total Bought: {parseFloat(item.total_bought.format()) * 1_000_000_000}</div>
      {status === 'success' && <div>Purchased ✅</div>}
      {status === 'error' && <div>Something went wrong ❌</div>}
      {status === 'none' &&  <button onClick={handleBuyItem}>Buy Item</button>}
      {status === 'loading' && <div>Buying item..</div>}
    
    </div>
  );
}

ListItem.tsx

import { useState } from "react";
import { ContractAbi } from "../contracts";
import { bn } from "fuels";
 
interface ListItemsProps {
  contract: ContractAbi | null;
}
 
export default function ListItem({contract}: ListItemsProps){
    const [metadata, setMetadata] = useState<string>("");
    const [price, setPrice] = useState<string>("0");
    const [status, setStatus] = useState<'success' | 'error' | 'loading' | 'none'>('none');
 
    async function handleSubmit(e: React.FormEvent<HTMLFormElement>){
        e.preventDefault();
        setStatus('loading')
        if(contract !== null){
            try {
                const priceInput = bn.parseUnits(price.toString());
                await contract.functions.list_item(priceInput, metadata).call();
                setStatus('success')
            } catch (e) {
                console.log("ERROR:", e);
                setStatus('error')
            }
        } else {
            console.log("ERROR: Contract is null");
        }
    }
    
    return (
        <div>
            <h2>List an Item</h2>
            {status === 'none' &&
            <form onSubmit={handleSubmit}>
                <div className="form-control">
                    <label htmlFor="metadata">Item Metadata:</label>
                    <input 
                        id="metadata" 
                        type="text" 
                        pattern="\w{20}" 
                        title="The metatdata must be 20 characters"
                        required 
                        onChange={(e) => setMetadata(e.target.value)}
                    />
                </div>
 
                <div className="form-control">
                    <label htmlFor="price">Item Price:</label>
                    <input
                        id="price"
                        type="number"
                        required
                        min="0"
                        step="any"
                        inputMode="decimal"
                        placeholder="0.00"
                        onChange={(e) => {
                          setPrice(e.target.value);
                        }}
                      />
                </div>
 
                <div className="form-control">
                    <button type="submit">List item</button>
                </div>
            </form>
            }
 
            {status === 'success' && <div>Item successfully listed!</div>}
            {status === 'error' && <div>Error listing item. Please try again.</div>}
            {status === 'loading' && <div>Listing item...</div>}
        </div>
    )
}

And that's it for the frontend! You just created a whole dapp on Fuel!

Was this page helpful?