diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 9e95d2fbd..005d101aa 100644 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -6,7 +6,7 @@ import { useLocation, useRouteMatch, } from "react-router-dom"; -import { IntlProvider, CustomFormats } from "react-intl"; +import { IntlProvider, CustomFormats, FormattedMessage } from "react-intl"; import { Helmet } from "react-helmet"; import cloneDeep from "lodash-es/cloneDeep"; import mergeWith from "lodash-es/mergeWith"; @@ -49,6 +49,7 @@ import { ConnectionMonitor } from "./ConnectionMonitor"; import { PatchFunction } from "./patch"; import moment from "moment/min/moment-with-locales"; +import { ErrorMessage } from "./components/Shared/ErrorMessage"; const Performers = lazyComponent( () => import("./components/Performers/Performers") @@ -102,6 +103,14 @@ const AppContainer: React.FC> = PatchFunction( } ) as React.FC; +const MainContainer: React.FC = ({ children }) => { + return ( +
+ {children} +
+ ); +}; + function translateLanguageLocale(l: string) { // intl doesn't support all locales, so we need to map some to supported ones switch (l) { @@ -287,14 +296,40 @@ export const App: React.FC = () => { const titleProps = makeTitleProps(); + if (!messages) { + return null; + } + + if (config.error) { + return ( + + + + } + error={config.error.message} + /> + + + ); + } + return ( - {messages ? ( - + + { loading={config.loading} > {maybeRenderReleaseNotes()} - - - }> - - - - - {maybeRenderNavbar()} -
- {renderContent()} -
-
-
-
-
-
+ + }> + + + + + {maybeRenderNavbar()} + {renderContent()} + + + +
-
- ) : null} + +
); }; diff --git a/ui/v2.5/src/components/List/PagedList.tsx b/ui/v2.5/src/components/List/PagedList.tsx index bf34f4fdd..ab27e2697 100644 --- a/ui/v2.5/src/components/List/PagedList.tsx +++ b/ui/v2.5/src/components/List/PagedList.tsx @@ -3,6 +3,8 @@ import { QueryResult } from "@apollo/client"; import { ListFilterModel } from "src/models/list-filter/filter"; import { Pagination, PaginationIndex } from "./Pagination"; import { LoadingIndicator } from "../Shared/LoadingIndicator"; +import { ErrorMessage } from "../Shared/ErrorMessage"; +import { FormattedMessage } from "react-intl"; export const PagedList: React.FC< PropsWithChildren<{ @@ -65,7 +67,17 @@ export const PagedList: React.FC< return ; } if (result.error) { - return

{result.error.message}

; + return ( + + } + error={result.error.message} + /> + ); } return ( diff --git a/ui/v2.5/src/components/Shared/ErrorMessage.tsx b/ui/v2.5/src/components/Shared/ErrorMessage.tsx index b0cdb12f3..1263e9c8a 100644 --- a/ui/v2.5/src/components/Shared/ErrorMessage.tsx +++ b/ui/v2.5/src/components/Shared/ErrorMessage.tsx @@ -1,11 +1,26 @@ +import { faWarning } from "@fortawesome/free-solid-svg-icons"; import React, { ReactNode } from "react"; +import { Alert } from "react-bootstrap"; +import { FormattedMessage } from "react-intl"; +import { Icon } from "./Icon"; interface IProps { + message?: React.ReactNode; error: string | ReactNode; } -export const ErrorMessage: React.FC = ({ error }) => ( -
-

Error: {error}

-
-); +export const ErrorMessage: React.FC = (props) => { + const { error, message = } = props; + + return ( +
+ + + + {message} + +
{error}
+
+
+ ); +}; diff --git a/ui/v2.5/src/components/Shared/styles.scss b/ui/v2.5/src/components/Shared/styles.scss index 4b0748fe6..342d19655 100644 --- a/ui/v2.5/src/components/Shared/styles.scss +++ b/ui/v2.5/src/components/Shared/styles.scss @@ -213,13 +213,29 @@ button.collapse-button { text-align: center; } -.ErrorMessage { - align-items: center; - height: 20rem; +.ErrorMessage-container { + display: flex; justify-content: center; + width: 100%; +} - &-content { - display: inline-block; +.ErrorMessage { + .fa-icon { + color: $warning; + font-size: 1.5em; + margin-right: 0.3em; + vertical-align: middle; + } + + background-color: initial; + border-color: $danger; + color: $text-color; + margin: 1rem; + text-align: left; + width: 500px; + + @include media-breakpoint-down(xs) { + width: 100%; } } diff --git a/ui/v2.5/src/hooks/Toast.tsx b/ui/v2.5/src/hooks/Toast.tsx index ef5c049e8..9be27e928 100644 --- a/ui/v2.5/src/hooks/Toast.tsx +++ b/ui/v2.5/src/hooks/Toast.tsx @@ -28,7 +28,9 @@ const errorDelay = 5000; let toastID = 0; -const ToastContext = createContext<(item: IToast) => void>(() => {}); +type ToastFn = (item: IToast) => void; + +const ToastContext = createContext(null); export const ToastProvider: React.FC = ({ children }) => { const [toast, setToast] = useState(); @@ -121,6 +123,10 @@ export const ToastProvider: React.FC = ({ children }) => { export const useToast = () => { const addToast = useContext(ToastContext); + if (!addToast) { + throw new Error("useToast must be used within a ToastProvider"); + } + return useMemo( () => ({ toast: addToast, diff --git a/ui/v2.5/src/plugins.tsx b/ui/v2.5/src/plugins.tsx index 256e1e5ce..41577a92c 100644 --- a/ui/v2.5/src/plugins.tsx +++ b/ui/v2.5/src/plugins.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { PatchFunction } from "./patch"; import { usePlugins } from "./core/StashService"; import { useMemoOnce } from "./hooks/state"; @@ -7,6 +7,7 @@ import useScript, { useCSS } from "./hooks/useScript"; import { PluginsQuery } from "./core/generated-graphql"; import { LoadingIndicator } from "./components/Shared/LoadingIndicator"; import { FormattedMessage } from "react-intl"; +import { useToast } from "./hooks/Toast"; type PluginList = NonNullable>; @@ -102,15 +103,25 @@ function useLoadPlugins() { ); useCSS(pluginCSS ?? [], !pluginsLoading && !pluginsError); - return !pluginsLoading && !!pluginJavascripts && pluginJavascriptLoaded; + return { + loading: !pluginsLoading && !!pluginJavascripts && pluginJavascriptLoaded, + error: pluginsError, + }; } export const PluginsLoader: React.FC> = ({ children, }) => { - const loaded = useLoadPlugins(); + const Toast = useToast(); + const { loading: loaded, error } = useLoadPlugins(); - if (!loaded) + useEffect(() => { + if (error) { + Toast.error(`Error loading plugins: ${error.message}`); + } + }, [Toast, error]); + + if (!loaded && !error) return ( } /> );