diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx
index 36f0fd3902..bbe428c523 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx
+++ b/packages/pluggableWidgets/file-uploader-web/src/components/ActionsBar.tsx
@@ -59,11 +59,7 @@ const DefaultActionsBar = observer(function DefaultActionsBar(props: ButtonsBarP
}, [props.store]);
if (props.store.fileStatus === "rejected") {
- return (
-
-
-
- );
+ return ;
}
return (
@@ -84,6 +80,26 @@ const DefaultActionsBar = observer(function DefaultActionsBar(props: ButtonsBarP
);
});
+function RejectedActionsBar({ store }: ButtonsBarProps): ReactElement {
+ const translations = useTranslationsStore();
+
+ const onDismiss = useCallback(() => {
+ store.dismiss();
+ }, [store]);
+
+ return (
+
+
+
}
+ title={translations.get("removeButtonTextMessage")}
+ action={onDismiss}
+ isDisabled={false}
+ />
+
+ );
+}
+
function DismissActionsBar({ store }: ButtonsBarProps): ReactElement {
const translations = useTranslationsStore();
diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx
index d8b4f51b3b..764b7c7cf0 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx
+++ b/packages/pluggableWidgets/file-uploader-web/src/components/Dropzone.tsx
@@ -16,7 +16,7 @@ interface DropzoneProps {
export const Dropzone = observer(
({ warningMessage, onDrop, maxSize, acceptFileTypes, disabled }: DropzoneProps): ReactElement => {
- const { getRootProps, getInputProps, isDragAccept, isDragReject } = useDropzone({
+ const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject } = useDropzone({
onDrop,
maxSize: maxSize || undefined,
accept: acceptFileTypes,
@@ -24,7 +24,12 @@ export const Dropzone = observer(
});
const translations = useTranslationsStore();
- const [type, msg] = getMessage(translations, isDragAccept, isDragReject);
+ const [type, msg] = getMessage(
+ translations,
+ isDragActive && isDragAccept,
+ isDragActive && isDragReject,
+ warningMessage
+ );
return (
@@ -32,7 +37,7 @@ export const Dropzone = observer(
className={classNames("dropzone", {
active: type === "active",
disabled,
- warning: !!warningMessage || type === "warning"
+ warning: type === "warning"
})}
{...getRootProps()}
>
@@ -52,7 +57,8 @@ type MessageType = "active" | "warning" | "idle";
function getMessage(
translations: TranslationsStore,
isDragAccept: boolean,
- isDragReject: boolean
+ isDragReject: boolean,
+ warningMessage?: string
): [MessageType, string] {
if (isDragAccept) {
return ["active", translations.get("dropzoneAcceptedMessage")];
@@ -60,6 +66,9 @@ function getMessage(
if (isDragReject) {
return ["warning", translations.get("dropzoneRejectedMessage")];
}
+ if (warningMessage) {
+ return ["warning", warningMessage];
+ }
return ["idle", translations.get("dropzoneIdleMessage")];
}
diff --git a/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx b/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx
index 519ca7d642..a7e0a1f9e4 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx
+++ b/packages/pluggableWidgets/file-uploader-web/src/components/FileUploaderRoot.tsx
@@ -28,6 +28,8 @@ export const FileUploaderRoot = observer((props: FileUploaderContainerProps): Re
warningMessage = translations.get("uploadLimitReachedMessage", rootStore.maxTotalFiles.toString());
} else if (rootStore.createActionFailed) {
warningMessage = translations.get("unavailableCreateActionMessage");
+ } else if (rootStore.files.some(f => f.fileStatus === "validationError")) {
+ warningMessage = translations.get("dropzoneRejectedMessage");
}
return (
diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts
index 9c78942c45..9fb7b02f1c 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts
+++ b/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts
@@ -190,8 +190,8 @@ export class FileUploaderStore {
get sortedFiles(): FileStore[] {
return [...this.files].sort((a, b) => {
- const isErrorA = a.fileStatus === "validationError" ? 1 : 0;
- const isErrorB = b.fileStatus === "validationError" ? 1 : 0;
+ const isErrorA = a.fileStatus === "validationError" || a.fileStatus === "rejected" ? 1 : 0;
+ const isErrorB = b.fileStatus === "validationError" || b.fileStatus === "rejected" ? 1 : 0;
return isErrorA - isErrorB;
});
}
diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts
index b544f88ad5..722ea57bdf 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts
+++ b/packages/pluggableWidgets/file-uploader-web/src/stores/__tests__/FileUploaderStore.spec.ts
@@ -344,7 +344,7 @@ describe("FileStore.canRetry — reacts to freed slots", () => {
// ─── FileStore.retry ─────────────────────────────────────────────────────────
describe("FileStore.retry", () => {
- test("transitions rejected file to queued", () => {
+ test("transitions rejected file to uploading via queue reaction", () => {
const store = buildStore({
maxFilesPerUpload: dynamic(new Big(3)),
maxFilesPerBatch: unavailableDynamic()
@@ -1018,3 +1018,33 @@ describe("upload queue — end-to-end", () => {
expect(store.files.filter(f => f.fileStatus === "uploading")).toHaveLength(0);
});
});
+
+describe("FileUploaderStore validationError files tracking", () => {
+ test("has validationError files after rejected drop", () => {
+ const store = buildStore();
+ store.processDrop([], [{ file: makeFile("bad.exe"), errors: [{ code: "file-invalid-type", message: "bad" }] }]);
+ expect(store.files.some(f => f.fileStatus === "validationError")).toBe(true);
+ });
+
+ test("no validationError files after all are dismissed", () => {
+ const store = buildStore();
+ store.processDrop([], [{ file: makeFile("bad.exe"), errors: [{ code: "file-invalid-type", message: "bad" }] }]);
+ const errorFile = store.files.find(f => f.fileStatus === "validationError")!;
+ store.dismissFile(errorFile);
+ expect(store.files.some(f => f.fileStatus === "validationError")).toBe(false);
+ });
+
+ test("validationError files remain when one dismissed but others exist", () => {
+ const store = buildStore();
+ store.processDrop(
+ [],
+ [
+ { file: makeFile("a.exe"), errors: [{ code: "file-invalid-type", message: "bad" }] },
+ { file: makeFile("b.exe"), errors: [{ code: "file-invalid-type", message: "bad" }] }
+ ]
+ );
+ const firstError = store.files.find(f => f.fileStatus === "validationError")!;
+ store.dismissFile(firstError);
+ expect(store.files.some(f => f.fileStatus === "validationError")).toBe(true);
+ });
+});