Skip to content

08 Upload UI

JP Barbosa edited this page Sep 24, 2022 · 15 revisions

Upload UI

Diagram

Upload UI

Create upload component

code ./frontend/src/components/Upload.tsx
import React, { useEffect } from "react";
import AWS from "aws-sdk";
import * as uuid from "uuid";
import { useAppContext } from "../contexts/AppContext";
import { useUpload } from "../hooks/useUpload";
import { Status } from "./Status";

const { VITE_API_REGION, VITE_API_BUCKET_NAME, VITE_API_IDENTITY_POOL_ID } =
  import.meta.env;

const credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: VITE_API_IDENTITY_POOL_ID,
});

export const Upload: React.FC = () => {
  const { uploadId, setUploadId, setSelectedItem } = useAppContext();

  useEffect(() => {
    if (!uploadId) {
      resetState();
    }
  }, [uploadId]);

  const { uploading, uploadFile, uploadError, upload, resetState } = useUpload({
    region: VITE_API_REGION,
    credentials,
    bucket: VITE_API_BUCKET_NAME,
  });

  const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!e.target.files) {
      return;
    }

    const [file] = e.target.files;
    const id = uuid.v1();
    setSelectedItem(undefined);
    setUploadId(undefined);

    await upload({
      file,
      id,
      callback: () => {
        setUploadId(id);
      },
    });
  };

  return (
    <div id="upload">
      <Status
        status={[
          {
            condition: uploading,
            message: "Uploading...",
          },
        ]}
      />
      <h2>Upload</h2>
      <form>
        <input
          type="file"
          accept="image/*"
          onChange={handleUpload}
          multiple={false}
        />
      </form>
      <div className="scrollable">
        {uploadFile && (
          <div className="file">
            <div className="box success">{uploadFile.name}</div>
            <img src={URL.createObjectURL(uploadFile)} />
          </div>
        )}
        {uploadError && (
          <div className="box error">
            <pre>{JSON.stringify(uploadError, null, 4)}</pre>
          </div>
        )}
        <div className="box info">
          Only the last uploaded image will be displayed. Files are
          automatically removed from S3 after processing.
        </div>
      </div>
    </div>
  );
};

Create upload hook

code ./frontend/src/hooks/useUpload.ts
import { useState } from "react";
import AWS from "aws-sdk";

type UseUploadParams = {
  region: string;
  credentials: AWS.Credentials;
  bucket: string;
};

type UploadParams = {
  id: string;
  file: File;
  callback?: Function;
};

export const useUpload = ({ region, credentials, bucket }: UseUploadParams) => {
  const [uploading, setUploading] = useState(false);
  const [uploadFile, setUploadFile] = useState<File>();
  const [uploadError, setUploadError] = useState<AWS.AWSError>();

  AWS.config.update({
    region,
    credentials,
  });

  const s3 = new AWS.S3({
    params: {
      Bucket: bucket,
    },
  });

  const resetState = () => {
    setUploading(false);
    setUploadFile(undefined);
    setUploadError(undefined);
  };

  const upload = async ({ file, id, callback }: UploadParams) => {
    resetState();
    try {
      setUploading(true);
      await s3
        .upload({
          Bucket: bucket,
          Key: id,
          Body: file,
        })
        .promise();
      setUploadFile(file);
      if (callback) callback();
    } catch (error) {
      const awsError = error as AWS.AWSError;
      setUploadError(awsError);
    } finally {
      setUploading(false);
    }
  };

  return {
    resetState,
    upload,
    uploading,
    uploadFile,
    uploadError,
  };
};

Add upload to the app

code ./frontend/src/App.tsx
...
import { Upload } from "./components/Upload";

function App() {
  return (
    <div id="app">
      <AppContextProvider>
        <Header />
        <div className="content">
          <Upload />
        </div>
      </AppContextProvider>
    </div>
  );
}

export default App;

Start SST and React

npm start
(cd ./frontend && npm run dev)

Try to upload an image

A credentials error will be throw, which will be addressed in the next chapter.

image

Commit

git add .
git commit -m "Upload UI"

Next step

Cognito Auth