diff --git a/web/source/settings/components/error.tsx b/web/source/settings/components/error.tsx
index 15c3bccd4..a2b4772dc 100644
--- a/web/source/settings/components/error.tsx
+++ b/web/source/settings/components/error.tsx
@@ -17,7 +17,9 @@
along with this program. If not, see .
*/
-import React from "react";
+import { SerializedError } from "@reduxjs/toolkit";
+import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
+import React, { ReactNode } from "react";
function ErrorFallback({ error, resetErrorBoundary }) {
return (
@@ -44,39 +46,70 @@ function ErrorFallback({ error, resetErrorBoundary }) {
);
}
-function Error({ error }) {
- /* eslint-disable-next-line no-console */
- console.error("Rendering error:", error);
- let message;
+interface GtsError {
+ /**
+ * Error message returned from the API.
+ */
+ error: string;
- if (error.data != undefined) { // RTK Query error with data
- if (error.status) {
- message = (<>
- {error.status}: {error.data.error}
- {error.data.error_description &&
-
- {error.data.error_description}
-
- }
- >);
- } else {
- message = error.data.error;
- }
- } else if (error.name != undefined || error.type != undefined) { // JS error
- message = (<>
- {error.type && error.name}: {error.message}
- >);
- } else if (error.status && typeof error.error == "string") {
- message = (<>
- {error.status}: {error.error}
- >);
+ /**
+ * For OAuth errors: description of the error.
+ */
+ error_description?: string;
+}
+
+interface ErrorProps {
+ error: FetchBaseQueryError | SerializedError | Error | undefined;
+
+ /**
+ * Optional function to clear the error.
+ * If provided, rendered error will have
+ * a "dismiss" button.
+ */
+ reset?: () => void;
+}
+
+function Error({ error, reset }: ErrorProps) {
+ if (error === undefined) {
+ return null;
+ }
+
+ /* eslint-disable-next-line no-console */
+ console.error("caught error: ", error);
+
+ let message: ReactNode;
+ if ("status" in error) {
+ // RTK Query error with data.
+ const gtsError = error.data as GtsError;
+ const errMsg = gtsError.error_description ?? gtsError.error;
+ message = <>Code {error.status} {errMsg}>;
} else {
- message = error.message ?? error;
+ // SerializedError or Error.
+ const errMsg = error.message ?? JSON.stringify(error);
+ message = (
+ <>{error.name && `${error.name}: `}{errMsg}>
+ );
+ }
+
+ let className = "error";
+ if (reset) {
+ className += " with-dismiss";
}
return (
-
- {message}
+
+ {message}
+ { reset &&
+
+ Dismiss
+
+
+ }
);
}
diff --git a/web/source/settings/components/form/inputs.tsx b/web/source/settings/components/form/inputs.tsx
index f82937fc1..c68095d95 100644
--- a/web/source/settings/components/form/inputs.tsx
+++ b/web/source/settings/components/form/inputs.tsx
@@ -29,6 +29,7 @@ import type {
RadioFormInputHook,
TextFormInputHook,
} from "../../lib/form/types";
+import { nanoid } from "nanoid";
export interface TextInputProps extends React.DetailedHTMLProps<
React.InputHTMLAttributes
,
@@ -92,22 +93,25 @@ export interface FileInputProps extends React.DetailedHTMLProps<
export function FileInput({ label, field, ...props }: FileInputProps) {
const { onChange, ref, infoComponent } = field;
+ const id = nanoid();
return (
);
}
diff --git a/web/source/settings/components/form/mutation-button.tsx b/web/source/settings/components/form/mutation-button.tsx
index 1e6d8c968..5d831cd24 100644
--- a/web/source/settings/components/form/mutation-button.tsx
+++ b/web/source/settings/components/form/mutation-button.tsx
@@ -51,9 +51,9 @@ export default function MutationButton({
}
return (
-
+
{(showError && targetsThisButton && result.error) &&
-
+
}