From b2a822bf086cf724855f63f6952ef28651616cfb Mon Sep 17 00:00:00 2001 From: Juan Di Toro Date: Tue, 26 Mar 2024 14:42:18 +0100 Subject: [PATCH] fix: error state for file upload --- app/components/general-layout.tsx | 48 ++++++++++++++++++++++++++--- app/components/icons.tsx | 31 ++++++++++++++++--- app/components/lib/uppy.ts | 6 +++- app/routes/file-manager/columns.tsx | 11 +++++-- 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/app/components/general-layout.tsx b/app/components/general-layout.tsx index a61ff90..2afff41 100644 --- a/app/components/general-layout.tsx +++ b/app/components/general-layout.tsx @@ -11,7 +11,7 @@ import { DialogTrigger, } from "~/components/ui/dialog"; import { useUppy } from "./lib/uppy"; -import type { UppyFile } from "@uppy/core"; +import type { FailedUppyFile, UppyFile } from "@uppy/core"; import { Progress } from "~/components/ui/progress"; import { DialogClose } from "@radix-ui/react-dialog"; import { ChevronDownIcon, ExitIcon, TrashIcon } from "@radix-ui/react-icons"; @@ -24,6 +24,7 @@ import { BoxCheckedIcon, PageIcon, ThemeIcon, + ExclamationCircleIcon, } from "./icons"; import { DropdownMenu, @@ -178,13 +179,15 @@ const UploadFileForm = () => { state, removeFile, cancelAll, + failedFiles, } = useUppy(); - console.log({ state, files: getFiles() }); - const isUploading = state === "uploading"; const isCompleted = state === "completed"; + const hasErrored = state === "error"; const hasStarted = state !== "idle" && state !== "initializing"; + const getFailedState = (id: string) => + failedFiles.find((file) => file.id === id); return ( <> @@ -216,11 +219,18 @@ const UploadFileForm = () => { onRemove={(id) => { removeFile(id); }} + failedState={getFailedState(file.id)} /> ))} + + {hasErrored ? ( +
+

An error occurred

+
+ ) : null} - {hasStarted ? ( + {hasStarted && !hasErrored ? (
{isCompleted @@ -260,19 +270,26 @@ function bytestoMegabytes(bytes: number) { const UploadFileItem = ({ file, + failedState, onRemove, }: { file: UppyFile; + failedState?: FailedUppyFile, Record>; onRemove: (id: string) => void; }) => { const sizeInMb = bytestoMegabytes(file.size).toFixed(2); return (
-
+
{file.progress?.uploadComplete ? ( + ) : failedState?.error ? ( + ) : ( )} @@ -304,6 +321,27 @@ const UploadFileItem = ({
+ {failedState ? ( +
+

Error uploading: {failedState.error}

+
+ + +
+
+ ) : null} + {file.progress?.uploadStarted && !file.progress.uploadComplete ? ( ) : null} diff --git a/app/components/icons.tsx b/app/components/icons.tsx index 5ce636d..d2288ef 100644 --- a/app/components/icons.tsx +++ b/app/components/icons.tsx @@ -465,10 +465,10 @@ export const FingerPrintIcon = ({ className }: { className?: string }) => { fill="none" xmlns="http://www.w3.org/2000/svg" className={className}> - + ); }; @@ -566,3 +566,26 @@ export const RecentIcon = ({ className }: { className?: string }) => { ); }; + +export const ExclamationCircleIcon = ({ + className, +}: { + className?: string; +}) => { + return ( + + ); +}; diff --git a/app/components/lib/uppy.ts b/app/components/lib/uppy.ts index e2da3d4..acb9715 100644 --- a/app/components/lib/uppy.ts +++ b/app/components/lib/uppy.ts @@ -1,4 +1,4 @@ -import Uppy, { debugLogger, type State, type UppyFile } from "@uppy/core"; +import Uppy, { debugLogger, FailedUppyFile, type State, type UppyFile } from "@uppy/core"; import Tus from "@uppy/tus"; import toArray from "@uppy/utils/lib/toArray"; @@ -63,6 +63,8 @@ export function useUppy() { } | object >({}); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [failedFiles, setFailedFiles] = useState, Record>[]>([]) const getRootProps = useMemo( () => () => { return { @@ -197,6 +199,7 @@ export function useUppy() { } console.log("successful files:", result.successful); console.log("failed files:", result.failed); + setFailedFiles(result.failed); }); const setStateCb = (event: (typeof LISTENING_EVENTS)[number]) => { @@ -232,6 +235,7 @@ export function useUppy() { return { getFiles: () => uppyInstance.current?.getFiles() ?? [], error: uppyInstance.current?.getState, + failedFiles, state, upload: () => uppyInstance.current?.upload() ?? diff --git a/app/routes/file-manager/columns.tsx b/app/routes/file-manager/columns.tsx index 2f07bb3..a8e7df3 100644 --- a/app/routes/file-manager/columns.tsx +++ b/app/routes/file-manager/columns.tsx @@ -12,6 +12,7 @@ import { import { cn } from "~/utils"; import type { FileItem } from "~/data/file-provider"; +import { usePinning } from "~/hooks/usePinning"; // This type is used to define the shape of our data. // You can use a Zod schema here if you want. @@ -73,7 +74,9 @@ export const columns: ColumnDef[] = [ accessorKey: "actions", header: () => null, size: 20, - cell: ({ row }) => ( + cell: ({ row }) => { + const {unpin} = usePinning(); + return (
[] = [ - + { + unpin(row.getValue("cid")) + }}> Delete @@ -93,6 +98,6 @@ export const columns: ColumnDef[] = [
- ), + )}, }, ];