fix: error state for file upload

This commit is contained in:
Juan Di Toro 2024-03-26 14:42:18 +01:00
parent 2bca9ce939
commit b2a822bf08
4 changed files with 83 additions and 13 deletions

View File

@ -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)}
/>
))}
</div>
{hasStarted ? (
{hasErrored ? (
<div className="text-red-500">
<p>An error occurred</p>
</div>
) : null}
{hasStarted && !hasErrored ? (
<div className="flex flex-col items-center gap-y-2 w-full text-primary-1">
<CloudCheckIcon className="w-32 h-32" />
{isCompleted
@ -260,19 +270,26 @@ function bytestoMegabytes(bytes: number) {
const UploadFileItem = ({
file,
failedState,
onRemove,
}: {
file: UppyFile;
failedState?: FailedUppyFile<Record<string, any>, Record<string, any>>;
onRemove: (id: string) => void;
}) => {
const sizeInMb = bytestoMegabytes(file.size).toFixed(2);
return (
<div className="flex flex-col w-full py-4 px-2 bg-primary-dark">
<div className="flex text-primary-1 items-center justify-between">
<div
className={`flex items-center justify-between ${
failedState ? "text-red-500" : "text-primary-1"
}`}>
<div className="flex items-center">
<div className="p-2">
{file.progress?.uploadComplete ? (
<BoxCheckedIcon className="w-4 h-4" />
) : failedState?.error ? (
<ExclamationCircleIcon className="w-4 h-4" />
) : (
<PageIcon className="w-4 h-4" />
)}
@ -304,6 +321,27 @@ const UploadFileItem = ({
</Button>
</div>
{failedState ? (
<div className="mt-2 text-red-500 text-sm">
<p>Error uploading: {failedState.error}</p>
<div className="flex gap-2">
<Button
size={"sm"}
onClick={() => {
/* Retry upload function here */
}}>
Retry
</Button>
<Button
size={"sm"}
variant={"outline"}
onClick={() => onRemove(file.id)}>
Remove
</Button>
</div>
</div>
) : null}
{file.progress?.uploadStarted && !file.progress.uploadComplete ? (
<Progress max={100} value={file.progress.percentage} className="mt-2" />
) : null}

View File

@ -566,3 +566,26 @@ export const RecentIcon = ({ className }: { className?: string }) => {
</svg>
);
};
export const ExclamationCircleIcon = ({
className,
}: {
className?: string;
}) => {
return (
<svg
aria-hidden="true"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
xmlns="http://www.w3.org/2000/svg"
className={className}>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
);
};

View File

@ -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<FailedUppyFile<Record<string, any>, Record<string, any>>[]>([])
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() ??

View File

@ -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<FileItem>[] = [
accessorKey: "actions",
header: () => null,
size: 20,
cell: ({ row }) => (
cell: ({ row }) => {
const {unpin} = usePinning();
return (
<div className="flex w-5 items-center justify-between">
<DropdownMenu>
<DropdownMenuTrigger
@ -85,7 +88,9 @@ export const columns: ColumnDef<FileItem>[] = [
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<DropdownMenuItem variant="destructive" onClick={() => {
unpin(row.getValue("cid"))
}}>
<TrashIcon className="mr-2" />
Delete
</DropdownMenuItem>
@ -93,6 +98,6 @@ export const columns: ColumnDef<FileItem>[] = [
</DropdownMenuContent>
</DropdownMenu>
</div>
),
)},
},
];