fix: error state for file upload
This commit is contained in:
parent
2bca9ce939
commit
b2a822bf08
|
@ -11,7 +11,7 @@ import {
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "~/components/ui/dialog";
|
} from "~/components/ui/dialog";
|
||||||
import { useUppy } from "./lib/uppy";
|
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 { Progress } from "~/components/ui/progress";
|
||||||
import { DialogClose } from "@radix-ui/react-dialog";
|
import { DialogClose } from "@radix-ui/react-dialog";
|
||||||
import { ChevronDownIcon, ExitIcon, TrashIcon } from "@radix-ui/react-icons";
|
import { ChevronDownIcon, ExitIcon, TrashIcon } from "@radix-ui/react-icons";
|
||||||
|
@ -24,6 +24,7 @@ import {
|
||||||
BoxCheckedIcon,
|
BoxCheckedIcon,
|
||||||
PageIcon,
|
PageIcon,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
|
ExclamationCircleIcon,
|
||||||
} from "./icons";
|
} from "./icons";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
@ -178,13 +179,15 @@ const UploadFileForm = () => {
|
||||||
state,
|
state,
|
||||||
removeFile,
|
removeFile,
|
||||||
cancelAll,
|
cancelAll,
|
||||||
|
failedFiles,
|
||||||
} = useUppy();
|
} = useUppy();
|
||||||
|
|
||||||
console.log({ state, files: getFiles() });
|
|
||||||
|
|
||||||
const isUploading = state === "uploading";
|
const isUploading = state === "uploading";
|
||||||
const isCompleted = state === "completed";
|
const isCompleted = state === "completed";
|
||||||
|
const hasErrored = state === "error";
|
||||||
const hasStarted = state !== "idle" && state !== "initializing";
|
const hasStarted = state !== "idle" && state !== "initializing";
|
||||||
|
const getFailedState = (id: string) =>
|
||||||
|
failedFiles.find((file) => file.id === id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -216,11 +219,18 @@ const UploadFileForm = () => {
|
||||||
onRemove={(id) => {
|
onRemove={(id) => {
|
||||||
removeFile(id);
|
removeFile(id);
|
||||||
}}
|
}}
|
||||||
|
failedState={getFailedState(file.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<div className="flex flex-col items-center gap-y-2 w-full text-primary-1">
|
||||||
<CloudCheckIcon className="w-32 h-32" />
|
<CloudCheckIcon className="w-32 h-32" />
|
||||||
{isCompleted
|
{isCompleted
|
||||||
|
@ -260,19 +270,26 @@ function bytestoMegabytes(bytes: number) {
|
||||||
|
|
||||||
const UploadFileItem = ({
|
const UploadFileItem = ({
|
||||||
file,
|
file,
|
||||||
|
failedState,
|
||||||
onRemove,
|
onRemove,
|
||||||
}: {
|
}: {
|
||||||
file: UppyFile;
|
file: UppyFile;
|
||||||
|
failedState?: FailedUppyFile<Record<string, any>, Record<string, any>>;
|
||||||
onRemove: (id: string) => void;
|
onRemove: (id: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const sizeInMb = bytestoMegabytes(file.size).toFixed(2);
|
const sizeInMb = bytestoMegabytes(file.size).toFixed(2);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full py-4 px-2 bg-primary-dark">
|
<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="flex items-center">
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
{file.progress?.uploadComplete ? (
|
{file.progress?.uploadComplete ? (
|
||||||
<BoxCheckedIcon className="w-4 h-4" />
|
<BoxCheckedIcon className="w-4 h-4" />
|
||||||
|
) : failedState?.error ? (
|
||||||
|
<ExclamationCircleIcon className="w-4 h-4" />
|
||||||
) : (
|
) : (
|
||||||
<PageIcon className="w-4 h-4" />
|
<PageIcon className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
|
@ -304,6 +321,27 @@ const UploadFileItem = ({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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 ? (
|
{file.progress?.uploadStarted && !file.progress.uploadComplete ? (
|
||||||
<Progress max={100} value={file.progress.percentage} className="mt-2" />
|
<Progress max={100} value={file.progress.percentage} className="mt-2" />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -465,10 +465,10 @@ export const FingerPrintIcon = ({ className }: { className?: string }) => {
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
className={className}>
|
className={className}>
|
||||||
<path
|
<path
|
||||||
d="M11.1728 0.931152C13.3952 0.931152 15.5267 1.81402 17.0982 3.38554C18.6697 4.95706 19.5526 7.0885 19.5526 9.31096V13.0353C19.5535 14.3774 19.2318 15.7001 18.6144 16.8918C17.997 18.0835 17.1021 19.1092 16.0051 19.8826C16.4465 18.4654 16.7035 16.9673 16.7519 15.4161L16.7593 14.8975V13.0344H14.8971V14.8975L14.8944 15.1908C14.8574 17.2213 14.4039 19.2225 13.562 21.0706C12.4834 21.3893 11.3519 21.4887 10.2342 21.363C11.3872 19.5425 12.0306 17.4461 12.0974 15.2923L12.1039 14.8975V8.37987H10.2417V14.8975L10.238 15.1657C10.1874 17.2324 9.5097 19.235 8.29479 20.9077C7.40642 20.5826 6.57967 20.1091 5.84975 19.5073C6.82259 18.2772 7.38005 16.77 7.44191 15.2029L7.44843 14.8975V9.31096L7.45308 9.12474C7.47869 8.5973 7.61679 8.08142 7.85811 7.61172L7.96425 7.41899L6.61883 6.07356C5.99989 6.94167 5.64371 7.96949 5.59277 9.03443L5.58625 9.31096V14.8975L5.58252 15.107C5.54313 16.2036 5.17995 17.264 4.53877 18.1545C3.40438 16.6894 2.79013 14.8882 2.79298 13.0353V9.31096C2.79298 7.0885 3.67585 4.95706 5.24737 3.38554C6.81889 1.81402 8.95032 0.931152 11.1728 0.931152ZM11.1728 3.72442C10.0592 3.72442 9.02197 4.0503 8.15047 4.61175L7.93632 4.757L9.28081 6.10243C9.7871 5.80291 10.3575 5.62832 10.9447 5.59312L11.1728 5.5866L11.359 5.59126C12.2803 5.63731 13.1517 6.02362 13.8045 6.67537C14.4573 7.32712 14.845 8.19794 14.8925 9.11916L14.8971 9.31096V11.1731H16.7593V9.31096C16.7593 7.82932 16.1707 6.40836 15.1231 5.36068C14.0754 4.313 12.6544 3.72442 11.1728 3.72442Z"
|
d="M11.1728 0.931152C13.3952 0.931152 15.5267 1.81402 17.0982 3.38554C18.6697 4.95706 19.5526 7.0885 19.5526 9.31096V13.0353C19.5535 14.3774 19.2318 15.7001 18.6144 16.8918C17.997 18.0835 17.1021 19.1092 16.0051 19.8826C16.4465 18.4654 16.7035 16.9673 16.7519 15.4161L16.7593 14.8975V13.0344H14.8971V14.8975L14.8944 15.1908C14.8574 17.2213 14.4039 19.2225 13.562 21.0706C12.4834 21.3893 11.3519 21.4887 10.2342 21.363C11.3872 19.5425 12.0306 17.4461 12.0974 15.2923L12.1039 14.8975V8.37987H10.2417V14.8975L10.238 15.1657C10.1874 17.2324 9.5097 19.235 8.29479 20.9077C7.40642 20.5826 6.57967 20.1091 5.84975 19.5073C6.82259 18.2772 7.38005 16.77 7.44191 15.2029L7.44843 14.8975V9.31096L7.45308 9.12474C7.47869 8.5973 7.61679 8.08142 7.85811 7.61172L7.96425 7.41899L6.61883 6.07356C5.99989 6.94167 5.64371 7.96949 5.59277 9.03443L5.58625 9.31096V14.8975L5.58252 15.107C5.54313 16.2036 5.17995 17.264 4.53877 18.1545C3.40438 16.6894 2.79013 14.8882 2.79298 13.0353V9.31096C2.79298 7.0885 3.67585 4.95706 5.24737 3.38554C6.81889 1.81402 8.95032 0.931152 11.1728 0.931152ZM11.1728 3.72442C10.0592 3.72442 9.02197 4.0503 8.15047 4.61175L7.93632 4.757L9.28081 6.10243C9.7871 5.80291 10.3575 5.62832 10.9447 5.59312L11.1728 5.5866L11.359 5.59126C12.2803 5.63731 13.1517 6.02362 13.8045 6.67537C14.4573 7.32712 14.845 8.19794 14.8925 9.11916L14.8971 9.31096V11.1731H16.7593V9.31096C16.7593 7.82932 16.1707 6.40836 15.1231 5.36068C14.0754 4.313 12.6544 3.72442 11.1728 3.72442Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -566,3 +566,26 @@ export const RecentIcon = ({ className }: { className?: string }) => {
|
||||||
</svg>
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -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 Tus from "@uppy/tus";
|
||||||
import toArray from "@uppy/utils/lib/toArray";
|
import toArray from "@uppy/utils/lib/toArray";
|
||||||
|
@ -63,6 +63,8 @@ export function useUppy() {
|
||||||
}
|
}
|
||||||
| object
|
| object
|
||||||
>({});
|
>({});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const [failedFiles, setFailedFiles] = useState<FailedUppyFile<Record<string, any>, Record<string, any>>[]>([])
|
||||||
const getRootProps = useMemo(
|
const getRootProps = useMemo(
|
||||||
() => () => {
|
() => () => {
|
||||||
return {
|
return {
|
||||||
|
@ -197,6 +199,7 @@ export function useUppy() {
|
||||||
}
|
}
|
||||||
console.log("successful files:", result.successful);
|
console.log("successful files:", result.successful);
|
||||||
console.log("failed files:", result.failed);
|
console.log("failed files:", result.failed);
|
||||||
|
setFailedFiles(result.failed);
|
||||||
});
|
});
|
||||||
|
|
||||||
const setStateCb = (event: (typeof LISTENING_EVENTS)[number]) => {
|
const setStateCb = (event: (typeof LISTENING_EVENTS)[number]) => {
|
||||||
|
@ -232,6 +235,7 @@ export function useUppy() {
|
||||||
return {
|
return {
|
||||||
getFiles: () => uppyInstance.current?.getFiles() ?? [],
|
getFiles: () => uppyInstance.current?.getFiles() ?? [],
|
||||||
error: uppyInstance.current?.getState,
|
error: uppyInstance.current?.getState,
|
||||||
|
failedFiles,
|
||||||
state,
|
state,
|
||||||
upload: () =>
|
upload: () =>
|
||||||
uppyInstance.current?.upload() ??
|
uppyInstance.current?.upload() ??
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
|
|
||||||
import { cn } from "~/utils";
|
import { cn } from "~/utils";
|
||||||
import type { FileItem } from "~/data/file-provider";
|
import type { FileItem } from "~/data/file-provider";
|
||||||
|
import { usePinning } from "~/hooks/usePinning";
|
||||||
|
|
||||||
// This type is used to define the shape of our data.
|
// This type is used to define the shape of our data.
|
||||||
// You can use a Zod schema here if you want.
|
// You can use a Zod schema here if you want.
|
||||||
|
@ -73,7 +74,9 @@ export const columns: ColumnDef<FileItem>[] = [
|
||||||
accessorKey: "actions",
|
accessorKey: "actions",
|
||||||
header: () => null,
|
header: () => null,
|
||||||
size: 20,
|
size: 20,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => {
|
||||||
|
const {unpin} = usePinning();
|
||||||
|
return (
|
||||||
<div className="flex w-5 items-center justify-between">
|
<div className="flex w-5 items-center justify-between">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
|
@ -85,7 +88,9 @@ export const columns: ColumnDef<FileItem>[] = [
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuItem variant="destructive">
|
<DropdownMenuItem variant="destructive" onClick={() => {
|
||||||
|
unpin(row.getValue("cid"))
|
||||||
|
}}>
|
||||||
<TrashIcon className="mr-2" />
|
<TrashIcon className="mr-2" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
@ -93,6 +98,6 @@ export const columns: ColumnDef<FileItem>[] = [
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
),
|
)},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in New Issue