# Building a Censorship Resistant Image Uploader


*TL;DR: You can check out [a repository with the complete code on GitHub](https://github.com/kay-is/ipfs-image-uploader), and also [a styled version of the app on IPFS](https://w3s.link/ipfs/bafybeiaalncy2lccyyuylvzuciunmcindqe7wd2gwey2ct6rukouylngc4).*

In the past, building applications that are censorship resistant but are still easy to use wasn't straightforward. 

Meddling around with pinning services or running your own IPFS node usually isn't what the average frontend dev knows how to deal with. Luckily, someone understood that issue and created [web3.storage](https://web3.storage/), a service that works as a layer between your Web2 skills and the shiny new decentralized world.

I try to teach frontend developers skills that make them full stack in a way that allows them as much of their frontend skills as possible. web3.storage is a perfect service for this; with [its new w3up API](https://blog.web3.storage/posts/w3up-for-ipfs-users), it got even more flexible!

In this article, I'll show you how to build an image uploader that doesn't cost a dime!

[![D_D Newsletter CTA](https://sitemedia.ams3.digitaloceanspaces.com/blog_banner_v1_d1653cce08.png)](https://devdao.to/blog-newsletter-1)

## What?

We will build a web app that lets people register an email and upload images they can then share, either with an HTTP URL or [an IPFS Content ID (CID)](https://docs.ipfs.tech/concepts/content-addressing/).

web3.storage handles the upload for free, and the account is owned by your user directly, so you don't have to pay for your user's uploads. 

## Prerequisites

You need a current version of [Node.js](https://nodejs.org/en/) because we use the [Preact-CLI](https://github.com/preactjs/preact-cli/) and some NPM packages. 

## Initializing the Project

First, run the following command to create a new Preact project:

    $ npx preact-cli create default quicksave

This command will generate a basic Preact project. A simple PWA structure with a service worker, manifest, and everything.

## Adding w3ui Packages

[web3.storage is currently adding a new API called w3up](https://blog.web3.storage/posts/w3up-beta-launch). It comes with a few improvements over the previous API, but the biggest one is that users can register for it on their own accounts directly via the API.

They even created [a UI library](https://github.com/web3-storage/w3ui) with a few React components out of the box. 

Okay, "components" is a bit of a stretch here; the packages come with 
 a few providers and hooks, but they are still a time saver. Plus, you can build a UI, but you don't need to provide your users with your own account.

Run this command to install the packages:

    $ npm i @w3ui/react-keyring \
      @w3ui/react-uploader \ 
      @w3ui/react-uploads-list

- The `@w3ui/react-keyring` package links every agent, in our case the browsers, to an account with an  email address.
- The `@w3ui/react-uploader ` package takes care of the file uploads.
- The `@w3ui/react-uploads-list` package lets you retrieve the CIDs of the uploaded files.

With these three packages, your users can signup for your app, upload their files and share them via an URL or a CID.

## Integrating w3ui

As I mentioned, the w3ui libraries only come with provider components and hooks; this makes them flexible and requires us to build some actual UI around them.

So, let's start by adding the providers to our Preact app.

### Adding Providers

In the `src/components/app.js` file, replace the code with this:

```jsx
import { AuthProvider } from "@w3ui/react-keyring"
import { UploaderProvider } from "@w3ui/react-uploader"
import { UploadsListProvider } from "@w3ui/react-uploads-list"

import Home from "./home"

const App = () => (
  <AuthProvider>
    <UploaderProvider>
      <UploadsListProvider>
        <Home />
      </UploadsListProvider>
    </UploaderProvider>
  </AuthProvider>
)

export default App
```

Nothing exciting here; just wrapping all app components with the providers.

### Creating the `Home` Component

Next is the `Home` component, simply a container for the components that will do all the work.

Create a `src/component/home.js` file and add this code:

```jsx
import Auth from "./auth"
import Uploader from "./uploader"
import UploadList from "./uploadsList"

const Home = (props) => {
  return (
    <div>
      <button type="button" onClick={() => props.unloadAndRemoveIdentity()}>
        Logout
      </button>
      <br />
      <Uploader />
      <br />
      <UploadList />
    </div>
  )
}

export default Auth(Home)
```

The `Home` component is wrapped in a higher order `Auth` component, which blocks access to `Home` until a user registers. `Auth` also injects a `unloadAndRemoveIdentity` function to `Home` we use for a logout button.

### Creating the `Auth` Component

The `Auth` component ensures that a user registers before using the app. Its [a higher order component](https://dev.to/kayis/higher-order-what) that wraps `Home` and displays the `Register` component when a user isn't registered.

Create it at `src/component/auth` with the following code:

```jsx
import { useEffect } from "preact/hooks"
import { useAuth, AuthStatus } from "@w3ui/react-keyring"
import Register from "./register"

export default function (Wrappee) {
  return function AuthWrapper(props) {
    const { authStatus, loadDefaultIdentity, unloadAndRemoveIdentity } =
      useAuth()

    useEffect(() => {
      loadDefaultIdentity()
    }, [])

    if (authStatus === AuthStatus.SignedIn) {
      const authProps = { ...props, unloadAndRemoveIdentity }
      return <Wrappee {...authProps} />
    }

    return <Register />
  }
}
```

The `loadDefaultIdentity` is called to check if the current browser is already registered.

### Creating the `Register` Component

The register component adds HTML elements around the w3ui hooks to make them usable.

Create it at `src/component/register.js` and fill it with this:

```jsx
import { useReducer } from "preact/hooks"
import { useAuth, AuthStatus } from "@w3ui/react-keyring"

export default function Register() {
  const { authStatus, identity, registerAndStoreIdentity } = useAuth()
  const [state, setState] = useReducer(
    (state, update) => ({ ...state, ...update }),
    { email: "", loading: false }
  )

  const waitingForVerification = authStatus === AuthStatus.EmailVerification

  return (
    <>
      <h1>QuickSave</h1>
      <h2>Distribute your files via IPFS!</h2>

      <form
        onSubmit={(e) => {
          e.preventDefault()
          setState({ loading: true })
          registerAndStoreIdentity(state.email)
        }}
      >
        <div>
          <label>Email Address: </label>
          <input
            type="email"
            disabled={state.loading}
            value={state.email}
            onChange={(e) => setState({ email: e.target.value })}
          />
          <button type="submit" disabled={state.loading}>
            Register
          </button>
          {waitingForVerification && (
            <p>Waiting for verification of "{identity.email}" address...</p>
          )}
        </div>
      </form>
    </>
  )
}

```

Here is where the actual registration happens. We use a reducer to keep track of the state, in this case, an `email` variable for the registration and a `loading` variable to lock up the UI after we submit the email.

### Creating the `Uploader` Component

Now that we have taken care of the registration, we need to implement the app's essential features.

Let's start with the `Uploader` component that lets users ... well ... upload images.

Create the file at `src/components/uploader.js` and add this code:

```jsx
import { useReducer } from "preact/hooks"
import { useUploader } from "@w3ui/react-uploader"

export default function Uploader() {
  const [, uploader] = useUploader()
  const [state, setState] = useReducer(
    (state, update) => ({ ...state, ...update }),
    { file: null, loading: false }
  )

  const handleUploadSubmit = async (e) => {
    e.preventDefault()
    setState({ loading: true })
    await uploader.uploadFile(state.file)
    setState({ loading: false })
  }

  return (
    <form onSubmit={handleUploadSubmit}>
      <input
        type="file"
        accept="image/*"
        onChange={(e) => setState({ file: e.target.files[0] })}
        required
        disabled={state.loading}
      />
      <br />
      <button type="submit" disabled={state.loading}>
        Upload
      </button>
      <br />
      {state.loading && <p>Uploading file...</p>}
    </form>
  )
}
```

Again, we wrap a simple UI around the w3ui hooks. The `<input>` element is used to choose a file and filters the valid files for images.

### Creating the `UploadList` Component

Now that our users can upload images, it would be nice if they could share them. For that, we have to use the w3up API for the CIDs of the images already uploaded and display them in a usable way.

Let's create a new file at `src/components/uploadsList` and add this code to it:

```jsx
import { useEffect } from "preact/hooks"
import { useUploadsList } from "@w3ui/react-uploads-list"

export default function UploadsList() {
  const { data, error, reload } = useUploadsList()

  useEffect(() => {
    const intervalId = setInterval(() => reload(), 5000)
    return () => clearInterval(intervalId)
  }, [])

  if (error) return <p>{error.message}</p>

  console.log(data)

  return (
    <div>
      {data?.results?.length &&
        data.results.map(({ dataCid, carCids, uploadedAt }) => (
          <div>
            <hr />
            <img
              src={`https://w3s.link/ipfs/${dataCid}`}
              alt={`Image CID: ${dataCid}`}
            />
            <p>
              Uploaded At
              <br />
              {uploadedAt.toString()}
            </p>
            <p>
              Data CID
              <br />
              <code>{dataCid}</code>
            </p>
            <p>
              Image URL
              <br />
              <a href={`https://w3s.link/ipfs/${dataCid}`}>
                <code>https://w3s.link/ipfs/{dataCid}</code>
              </a>
            </p>
          </div>
        ))}
    </div>
  )
}
```

The list gets updated periodically every five seconds. 

The exciting part is the `dataCid`'s we get from the `useUploadsList` hook. It points directly to the image on IPFS. With an IPFS gateway, we can create a link we can put into an `<img>` tag and let the user share the file.

## Testing the App

To test the app, we can use the Preact-CLI. The following command with run the system in dev mode:

    $ npm run dev

The registration process requires you to enter an email. You have to verify it before you can start uploading.

## Bonus: Deploying the App

Now that everything is up and running locally, it would be nice to deploy it somewhere.

We could build the project and upload it to GitHub Pages, but wouldn't it be cooler to go for something more decentralized?

[The w3up tool suite has a CLI](https://github.com/web3-storage/w3up-cli) that lets you do just that. Give it a directory and get a CID that you can use to share with your peers.

Let's start by building the code:

    $ npm run build 

This will create a `build` directory, which we will tell `w3up` about, but first, we need to install it.

    $ npm i -g @web3-storage/w3up-cli

We need to register here too:

    $ w3up register <YOUR_EMAIL>

Now, we're ready to go!

    $ w3up upload ./build

This command will respond with two CIDs and a gateway URL if everything goes correctly. We can use this URL to access the app online.

[![D_D Newsletter CTA](https://sitemedia.ams3.digitaloceanspaces.com/blog_banner_v1_d1653cce08.png)](https://devdao.to/blog-newsletter-1)

## Summary

Building your own DApp with file hosting features has become easy with w3up and IPFS. 

The **w3up API is currently in beta**, so uploads are free, but if you check out the parent project web3.storage, you see that they offer a free tier with a few gigabytes, and the other plans are pretty affordable too.

The best thing is that this isn't your subscription; your users register and eventually pay for it if they hit the free tier limits. 

Plus, your app deployment and all the uploaded images are accessible via IPFS, which means anyone who thinks they shouldn't be deleted can pin the CIDs on their IPFS pinning service of choice or their own node and keep them available. 
