From 39493634744ce90f55bb648b3ec4b6a05e0c252e Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:24:40 +0100 Subject: [PATCH] Include actual error in snackbar --- public/locales/en.json | 6 +- src/lib/HelperFunctions.ts | 18 ++++ .../components/WebUIUpdateChecker.tsx | 5 +- src/modules/backup/screens/Backup.tsx | 9 +- src/modules/browse/screens/BrowseSettings.tsx | 9 +- .../components/CategoriesInclusionSetting.tsx | 5 +- .../category/components/CategorySelect.tsx | 5 +- src/modules/chapter/services/Chapters.ts | 7 +- src/modules/core/components/UpdateChecker.tsx | 5 +- .../snackbar/SnackbarWithDescription.tsx | 90 +++++++++++++++++++ src/modules/core/contexts/AppContext.tsx | 11 ++- src/modules/core/utils/Toast.ts | 23 +++-- src/modules/device/screens/DeviceSetting.tsx | 5 +- .../components/DownloadAheadSetting.tsx | 5 +- .../downloads/screens/DownloadQueue.tsx | 7 +- .../downloads/screens/DownloadSettings.tsx | 9 +- .../extension/components/ExtensionCard.tsx | 7 +- src/modules/extension/screens/Extensions.tsx | 7 +- .../components/LibraryOptionsPanel.tsx | 9 +- .../library/screens/LibrarySettings.tsx | 7 +- .../manga/components/TrackMangaButton.tsx | 2 +- .../hooks/useManageMangaLibraryState.tsx | 9 +- src/modules/manga/services/Mangas.ts | 7 +- src/modules/migration/screens/Migration.tsx | 5 +- src/modules/reader/services/ReaderService.ts | 13 ++- .../globalUpdate/GlobalUpdateSettings.tsx | 5 +- .../GlobalUpdateSettingsEntries.tsx | 5 +- .../webUI/WebUIUpdateIntervalSetting.tsx | 5 +- src/modules/settings/screens/Appearance.tsx | 5 +- .../settings/screens/ServerSettings.tsx | 7 +- src/modules/settings/screens/Settings.tsx | 3 +- .../settings/screens/WebUISettings.tsx | 7 +- .../source/screens/SourceConfigure.tsx | 5 +- src/modules/source/screens/SourceMangas.tsx | 5 +- .../theme/components/CreateThemeDialog.tsx | 5 +- src/modules/theme/components/ThemeList.tsx | 4 +- src/modules/theme/components/ThemePreview.tsx | 9 +- src/modules/tracker/components/TrackManga.tsx | 3 +- .../tracker/components/TrackerSearch.tsx | 3 +- .../components/cards/SettingsTrackerCard.tsx | 5 +- .../components/cards/TrackerActiveCard.tsx | 7 +- .../tracker/screens/TrackerOAuthLogin.tsx | 3 +- .../tracker/screens/TrackingSettings.tsx | 5 +- src/modules/updates/screens/Updates.tsx | 7 +- 44 files changed, 292 insertions(+), 91 deletions(-) create mode 100644 src/modules/core/components/snackbar/SnackbarWithDescription.tsx diff --git a/public/locales/en.json b/public/locales/en.json index bcd44455..39936cb5 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -408,6 +408,7 @@ "disabled": "Disabled", "discord": "Discord", "display": "Display", + "error": "Error", "filter": "Filter", "finished": "Finished", "footnote": "{{value}}<0>$t(global.label.asterisk)", @@ -415,6 +416,7 @@ "github": "GitHub", "hidden": "Hidden", "horizontal": "Horizontal", + "info": "Information", "left": "Left", "links": "Links", "load_in_progress": "Still loading required data…", @@ -435,10 +437,12 @@ "sort": "Sort", "standard": "Standard", "started": "Started", + "success": "Success", "type": "Type", "unknown": "Unknown", "username": "Username", - "vertical": "Vertical" + "vertical": "Vertical", + "warning": "Warning" }, "language": { "label": { diff --git a/src/lib/HelperFunctions.ts b/src/lib/HelperFunctions.ts index 24f20dd6..9353b05c 100644 --- a/src/lib/HelperFunctions.ts +++ b/src/lib/HelperFunctions.ts @@ -6,6 +6,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { ApolloError } from '@apollo/client/errors'; + export const jsonSaveParse = (...args: Parameters): T | null => { try { return JSON.parse(...args); @@ -13,3 +15,19 @@ export const jsonSaveParse = (...args: Parameters): return null; } }; + +export const getErrorMessage = (error: unknown): string => { + if (error instanceof ApolloError) { + return `${error.name}: ${error.message}`; + } + + if (error instanceof Error) { + return `${error.name}: ${error.message}`; + } + + if (error == null) { + return ''; + } + + return `${error}`; +}; diff --git a/src/modules/app-updates/components/WebUIUpdateChecker.tsx b/src/modules/app-updates/components/WebUIUpdateChecker.tsx index 29950ca5..c558d4df 100644 --- a/src/modules/app-updates/components/WebUIUpdateChecker.tsx +++ b/src/modules/app-updates/components/WebUIUpdateChecker.tsx @@ -23,6 +23,7 @@ import { ABOUT_WEBUI, WEBUI_UPDATE_CHECK } from '@/lib/graphql/fragments/InfoFra import { VersionUpdateInfoDialog } from '@/modules/app-updates/components/VersionUpdateInfoDialog.tsx'; import { useUpdateChecker } from '@/modules/app-updates/hooks/useUpdateChecker.tsx'; import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const disabledUpdateCheck = () => Promise.resolve(); @@ -136,7 +137,9 @@ export const WebUIUpdateChecker = () => { onAction={() => requestManager .updateWebUI() - .response.catch(() => makeToast(t('settings.about.webui.label.update_failure'), 'error')) + .response.catch((e) => + makeToast(t('settings.about.webui.label.update_failure'), 'error', getErrorMessage(e)), + ) } actionTitle={ isUpdateInProgress diff --git a/src/modules/backup/screens/Backup.tsx b/src/modules/backup/screens/Backup.tsx index c63da5b9..d21c767d 100644 --- a/src/modules/backup/screens/Backup.tsx +++ b/src/modules/backup/screens/Backup.tsx @@ -36,6 +36,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type BackupSettingsType = Pick; @@ -101,8 +102,8 @@ export function Backup() { setting: Setting, value: BackupSettingsType[Setting], ) => { - mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }; @@ -152,7 +153,7 @@ export function Backup() { return true; } catch (e) { - makeToast(t('settings.backup.action.validate.error.label.failure'), 'error'); + makeToast(t('settings.backup.action.validate.error.label.failure'), 'error', getErrorMessage(e)); resetBackupState(); } @@ -167,7 +168,7 @@ export function Backup() { backupRestoreId = response.data?.restoreBackup.id; setTriggerReRender(Date.now()); } catch (e) { - makeToast(t('settings.backup.action.restore.error.label.failure'), 'error'); + makeToast(t('settings.backup.action.restore.error.label.failure'), 'error', getErrorMessage(e)); } finally { resetBackupState(); } diff --git a/src/modules/browse/screens/BrowseSettings.tsx b/src/modules/browse/screens/BrowseSettings.tsx index f9a1282e..98d7c6e2 100644 --- a/src/modules/browse/screens/BrowseSettings.tsx +++ b/src/modules/browse/screens/BrowseSettings.tsx @@ -28,6 +28,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts' import { makeToast } from '@/modules/core/utils/Toast.ts'; import { MetadataBrowseSettings } from '@/modules/browse/Browse.types.ts'; import { ServerSettings as GqlServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type ExtensionsSettings = Pick; @@ -57,16 +58,16 @@ export const BrowseSettings = () => { setting: Setting, value: ExtensionsSettings[Setting], ) => { - mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }; const { settings: { hideLibraryEntries }, } = useMetadataServerSettings(); - const updateMetadataServerSettings = createUpdateMetadataServerSettings(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + const updateMetadataServerSettings = createUpdateMetadataServerSettings((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); if (loading) { diff --git a/src/modules/category/components/CategoriesInclusionSetting.tsx b/src/modules/category/components/CategoriesInclusionSetting.tsx index 50b6f8ae..df171a4b 100644 --- a/src/modules/category/components/CategoriesInclusionSetting.tsx +++ b/src/modules/category/components/CategoriesInclusionSetting.tsx @@ -28,6 +28,7 @@ import { CategoryNameInfo, CategoryUpdateInclusionInfo, } from '@/modules/category/Category.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type CategoryType = CategoryIdInfo & CategoryNameInfo & CategoryUpdateInclusionInfo & CategoryDownloadInclusionInfo; @@ -139,8 +140,8 @@ export const CategoriesInclusionSetting = ({ await Promise.all(categoriesToUpdate.map((category) => updateCategory(category))); // TODO - update cache immediately // mutate(categoriesEndpoint, [...dialogCategories], { revalidate: false }); - } catch (error) { - makeToast(t('global.error.label.failed_to_save_changes'), 'error'); + } catch (e) { + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)); // mutate(categoriesEndpoint, [...categories]); } }; diff --git a/src/modules/category/components/CategorySelect.tsx b/src/modules/category/components/CategorySelect.tsx index a44dc1a3..80341832 100644 --- a/src/modules/category/components/CategorySelect.tsx +++ b/src/modules/category/components/CategorySelect.tsx @@ -34,6 +34,7 @@ import { import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts'; import { GET_MANGA_CATEGORIES } from '@/lib/graphql/queries/MangaQuery.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type BaseProps = { open: boolean; @@ -150,8 +151,8 @@ export function CategorySelect(props: CategorySelectProps) { onClose(true, addToCategories, removeFromCategories); if (doNotShowAddToLibraryDialogAgain) { - updateMetadataServerSettings('showAddToLibraryCategorySelectDialog', false).catch(() => - makeToast(t('search.error.label.failed_to_save_settings'), 'error'), + updateMetadataServerSettings('showAddToLibraryCategorySelectDialog', false).catch((e) => + makeToast(t('search.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)), ); } diff --git a/src/modules/chapter/services/Chapters.ts b/src/modules/chapter/services/Chapters.ts index 38b207b2..3f18d591 100644 --- a/src/modules/chapter/services/Chapters.ts +++ b/src/modules/chapter/services/Chapters.ts @@ -23,6 +23,7 @@ import { DirectionOffset, TranslationKey } from '@/Base.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export type ChapterAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread'; @@ -279,7 +280,11 @@ export class Chapters { await fnToExecute(); makeToast(translate(actionToTranslationKey[action].success, { count: itemCount }), 'success'); } catch (e) { - makeToast(translate(actionToTranslationKey[action].error, { count: itemCount }), 'error'); + makeToast( + translate(actionToTranslationKey[action].error, { count: itemCount }), + 'error', + getErrorMessage(e), + ); throw e; } } diff --git a/src/modules/core/components/UpdateChecker.tsx b/src/modules/core/components/UpdateChecker.tsx index 87767408..5cfa444a 100644 --- a/src/modules/core/components/UpdateChecker.tsx +++ b/src/modules/core/components/UpdateChecker.tsx @@ -25,6 +25,7 @@ import { dateTimeFormatter } from '@/util/DateHelper.ts'; import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; import { CategoryIdInfo } from '@/modules/category/Category.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const calcProgress = (status: UpdaterSubscription['updateStatusChanged'] | undefined) => { if (!status) { @@ -93,7 +94,7 @@ export function UpdateChecker({ reFetchLastTimestamp().catch(defaultPromiseErrorHandler('UpdateChecker::reFetchLastTimestamp')); } catch (e) { lastRunningState = false; - makeToast(t('global.error.label.update_failed'), 'error'); + makeToast(t('global.error.label.update_failed'), 'error', getErrorMessage(e)); } }; @@ -101,7 +102,7 @@ export function UpdateChecker({ try { await requestManager.resetGlobalUpdate(); } catch (e) { - makeToast(t('library.error.label.stop_global_update'), 'error'); + makeToast(t('library.error.label.stop_global_update'), 'error', getErrorMessage(e)); } }; diff --git a/src/modules/core/components/snackbar/SnackbarWithDescription.tsx b/src/modules/core/components/snackbar/SnackbarWithDescription.tsx new file mode 100644 index 00000000..5cf9fef3 --- /dev/null +++ b/src/modules/core/components/snackbar/SnackbarWithDescription.tsx @@ -0,0 +1,90 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { CustomContentProps, SnackbarContent, VariantType } from 'notistack'; +import { ForwardedRef, forwardRef, memo } from 'react'; +import Alert from '@mui/material/Alert'; +import AlertTitle from '@mui/material/AlertTitle'; +import Button from '@mui/material/Button'; +import { useTranslation } from 'react-i18next'; +import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx'; +import { TranslationKey } from '@/Base.types.ts'; +import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; + +const MAX_DESCRIPTION_LENGTH = 255; + +const SNACKBAR_VARIANT_TO_TRANSLATION_KEY: Record = { + default: 'global.label.info', + info: 'global.label.info', + success: 'global.label.success', + warning: 'global.label.warning', + error: 'global.label.error', +}; + +export const SnackbarWithDescription = memo( + forwardRef( + ( + { + id, + message, + description, + variant, + action, + }: CustomContentProps & { + // eslint-disable-next-line react/no-unused-prop-types + description?: string; + }, + ref: ForwardedRef, + ) => { + const { t } = useTranslation(); + + const severity = variant === 'default' ? undefined : variant; + const finalAction = typeof action === 'function' ? action(id) : action; + + const isDescriptionTooLong = (description?.length ?? 0) > MAX_DESCRIPTION_LENGTH; + const actualDescription = isDescriptionTooLong + ? description?.slice(0, MAX_DESCRIPTION_LENGTH) + : description; + + return ( + + + {message} + {actualDescription}{' '} + {isDescriptionTooLong ? ( + + ) : ( + '' + )} + + + ); + }, + ), +); diff --git a/src/modules/core/contexts/AppContext.tsx b/src/modules/core/contexts/AppContext.tsx index d93eb7a1..93ed7bf5 100644 --- a/src/modules/core/contexts/AppContext.tsx +++ b/src/modules/core/contexts/AppContext.tsx @@ -26,6 +26,7 @@ import { useMetadataServerSettings } from '@/modules/settings/services/ServerSet import { ReaderContextProvider } from '@/modules/reader/contexts/ReaderContextProvider.tsx'; import { DIRECTION_TO_CACHE } from '@/modules/theme/ThemeDirectionCache.ts'; import { AppHotkeysProvider } from '@/modules/hotkeys/contexts/AppHotkeysProvider.tsx'; +import { SnackbarWithDescription } from '@/modules/core/components/snackbar/SnackbarWithDescription.tsx'; interface Props { children: React.ReactNode; @@ -84,7 +85,15 @@ export const AppContext: React.FC = ({ children }) => { - + {children} diff --git a/src/modules/core/utils/Toast.ts b/src/modules/core/utils/Toast.ts index 1206ba80..94fd4407 100644 --- a/src/modules/core/utils/Toast.ts +++ b/src/modules/core/utils/Toast.ts @@ -8,12 +8,21 @@ import { enqueueSnackbar, OptionsObject, SnackbarKey } from 'notistack'; -export function makeToast(message: string, severity?: OptionsObject['variant']): SnackbarKey; -export function makeToast(message: string, options?: OptionsObject): SnackbarKey; -export function makeToast(message: string, options: OptionsObject['variant'] | OptionsObject = 'default'): SnackbarKey { - if (typeof options === 'string') { - return enqueueSnackbar(message, { variant: options }); - } +export function makeToast(message: string, severity?: OptionsObject['variant'], description?: string): SnackbarKey; +export function makeToast(message: string, options?: OptionsObject, description?: string): SnackbarKey; +export function makeToast( + message: string, + options: OptionsObject['variant'] | OptionsObject = 'default', + description?: string, +): SnackbarKey { + const variant = typeof options === 'string' ? options : undefined; + const snackbarOptions = typeof options === 'object' ? options : {}; - return enqueueSnackbar(message, options); + return enqueueSnackbar(message, { + variant, + ...snackbarOptions, + // @ts-ignore - TS2353, "notistack" is outdated and the provided way to define custom props is not working, however, + // everything in the options object gets passed to the custom snackbar component + description, + }); } diff --git a/src/modules/device/screens/DeviceSetting.tsx b/src/modules/device/screens/DeviceSetting.tsx index 11476afe..1cf83b5f 100644 --- a/src/modules/device/screens/DeviceSetting.tsx +++ b/src/modules/device/screens/DeviceSetting.tsx @@ -26,6 +26,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { DEFAULT_DEVICE } from '@/modules/device/services/Device.ts'; import { MetadataServerSettingKeys, MetadataServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const DeviceSetting = () => { const { t } = useTranslation(); @@ -63,8 +64,8 @@ export const DeviceSetting = () => { setActiveDevice(DEFAULT_DEVICE); } - updateMetadataServerSettings(setting, value).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + updateMetadataServerSettings(setting, value).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }; diff --git a/src/modules/downloads/components/DownloadAheadSetting.tsx b/src/modules/downloads/components/DownloadAheadSetting.tsx index 75ed8e45..5232e20d 100644 --- a/src/modules/downloads/components/DownloadAheadSetting.tsx +++ b/src/modules/downloads/components/DownloadAheadSetting.tsx @@ -17,6 +17,7 @@ import { updateMetadataServerSettings } from '@/modules/settings/services/Server import { makeToast } from '@/modules/core/utils/Toast.ts'; import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts'; import { MetadataServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const MIN_LIMIT = 2; const MAX_LIMIT = 10; @@ -39,8 +40,8 @@ export const DownloadAheadSetting = ({ const updateSetting = (value: MetadataDownloadSettings['downloadAheadLimit']) => { persistDownloadAheadLimit(value === 0 ? currentDownloadAheadLimit : value); - updateMetadataServerSettings('downloadAheadLimit', value).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + updateMetadataServerSettings('downloadAheadLimit', value).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }; diff --git a/src/modules/downloads/screens/DownloadQueue.tsx b/src/modules/downloads/screens/DownloadQueue.tsx index aa7735e3..2df6bb63 100644 --- a/src/modules/downloads/screens/DownloadQueue.tsx +++ b/src/modules/downloads/screens/DownloadQueue.tsx @@ -36,6 +36,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts' import { ChapterDownloadStatus, ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts'; import { DownloaderState, DownloadState } from '@/lib/graphql/generated/graphql.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const HeightPreservingItem = ({ children, ...props }: BoxProps) => ( // the height is necessary to prevent the item container from collapsing, which confuses Virtuoso measurements @@ -141,7 +142,7 @@ export const DownloadQueue: React.FC = () => { try { await requestManager.clearDownloads().response; } catch (e) { - makeToast(t('download.queue.error.label.failed_delete_all'), 'error'); + makeToast(t('download.queue.error.label.failed_delete_all'), 'error', getErrorMessage(e)); } }; @@ -216,7 +217,7 @@ export const DownloadQueue: React.FC = () => { try { await requestManager.addChapterToDownloadQueue(chapter.id).response; } catch (e) { - makeToast(t('download.queue.error.label.failed_to_remove'), 'error'); + makeToast(t('download.queue.error.label.failed_to_remove'), 'error', getErrorMessage(e)); } }; @@ -237,7 +238,7 @@ export const DownloadQueue: React.FC = () => { requestManager.deleteDownloadedChapter(chapter.id).response, ]); } catch (e) { - makeToast(t('download.queue.error.label.failed_to_retry'), 'error'); + makeToast(t('download.queue.error.label.failed_to_retry'), 'error', getErrorMessage(e)); } if (!isRunning) { diff --git a/src/modules/downloads/screens/DownloadSettings.tsx b/src/modules/downloads/screens/DownloadSettings.tsx index cfcb51b9..e9269f90 100644 --- a/src/modules/downloads/screens/DownloadSettings.tsx +++ b/src/modules/downloads/screens/DownloadSettings.tsx @@ -32,6 +32,7 @@ import { GetCategoriesSettingsQuery, GetCategoriesSettingsQueryVariables } from import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts'; import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type DownloadSettingsType = Pick< ServerSettings, @@ -115,13 +116,13 @@ export const DownloadSettings = () => { setting: Setting, value: DownloadSettingsType[Setting], ) => { - mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }; - const updateMetadataSetting = createUpdateMetadataServerSettings(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + const updateMetadataSetting = createUpdateMetadataServerSettings((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); return ( diff --git a/src/modules/extension/components/ExtensionCard.tsx b/src/modules/extension/components/ExtensionCard.tsx index 3179127e..56cfd159 100644 --- a/src/modules/extension/components/ExtensionCard.tsx +++ b/src/modules/extension/components/ExtensionCard.tsx @@ -32,6 +32,7 @@ import { INSTALLED_STATE_TO_TRANSLATION_KEY_MAP, } from '@/modules/extension/Extensions.constants.ts'; import { getInstalledState } from '@/modules/extension/Extensions.utils.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; interface IProps { extension: TExtension; @@ -84,7 +85,11 @@ export function ExtensionCard(props: IProps) { handleUpdate(); } catch (e) { setInstalledState(getInstalledState(isInstalled, isObsolete, hasUpdate)); - makeToast(t(EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[action], { count: 1 }), 'error'); + makeToast( + t(EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[action], { count: 1 }), + 'error', + getErrorMessage(e), + ); } }; diff --git a/src/modules/extension/screens/Extensions.tsx b/src/modules/extension/screens/Extensions.tsx index a745d46d..88103edc 100644 --- a/src/modules/extension/screens/Extensions.tsx +++ b/src/modules/extension/screens/Extensions.tsx @@ -40,6 +40,7 @@ import { import { ExtensionAction, ExtensionGroupState, ExtensionState } from '@/modules/extension/Extensions.types.ts'; import { EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP } from '@/modules/extension/Extensions.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const LANGUAGE = 0; const EXTENSIONS = 1; @@ -123,7 +124,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) { handleExtensionUpdate(); makeToast(t('extension.label.installed_successfully'), 'success'); }) - .catch(() => makeToast(t('extension.label.installation_failed'), 'error')); + .catch((e) => makeToast(t('extension.label.installation_failed'), 'error', getErrorMessage(e))); } else { makeToast(t('global.error.label.invalid_file_type'), 'error'); } @@ -263,7 +264,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) { requestManager .updateExtensions(extensionIds, { update: true }) .response.then(() => handleExtensionUpdate()) - .catch(() => + .catch((e) => makeToast( t( EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[ @@ -271,6 +272,8 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) { ], { count: groupedExtensions.length }, ), + 'error', + getErrorMessage(e), ), ) .finally(() => setUpdatingExtensionIds([])); diff --git a/src/modules/library/components/LibraryOptionsPanel.tsx b/src/modules/library/components/LibraryOptionsPanel.tsx index 8125eb4a..f2761e57 100644 --- a/src/modules/library/components/LibraryOptionsPanel.tsx +++ b/src/modules/library/components/LibraryOptionsPanel.tsx @@ -29,6 +29,7 @@ import { LibrarySortMode } from '@/modules/library/Library.types.ts'; import { CategoryMetadataInfo } from '@/modules/category/Category.types.ts'; import { statusToTranslationKey } from '@/modules/manga/Manga.constants.ts'; import { GridLayout } from '@/modules/core/Core.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = { filter: 'global.label.filter', @@ -61,15 +62,15 @@ export const LibraryOptionsPanel = ({ const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []); const categoryLibraryOptions = useGetCategoryMetadata(category); - const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, () => - makeToast(t('global.error.label.failed_to_save_changes', 'error')), + const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, (e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); const { settings: { showTabSize }, } = useMetadataServerSettings(); - const setSettingValue = createUpdateMetadataServerSettings<'showTabSize'>(() => - makeToast(t('search.error.label.failed_to_save_settings'), 'warning'), + const setSettingValue = createUpdateMetadataServerSettings<'showTabSize'>((e) => + makeToast(t('search.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)), ); return ( diff --git a/src/modules/library/screens/LibrarySettings.tsx b/src/modules/library/screens/LibrarySettings.tsx index f6c8d5c2..873e6143 100644 --- a/src/modules/library/screens/LibrarySettings.tsx +++ b/src/modules/library/screens/LibrarySettings.tsx @@ -38,6 +38,7 @@ import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts' import { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts'; import { MetadataLibrarySettings } from '@/modules/library/Library.types.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const removeNonLibraryMangasFromCategories = async (): Promise => { try { @@ -57,7 +58,7 @@ const removeNonLibraryMangasFromCategories = async (): Promise => { } makeToast(translate('library.settings.advanced.database.cleanup.label.success'), 'success'); } catch (e) { - makeToast(translate('library.settings.advanced.database.cleanup.label.error'), 'error'); + makeToast(translate('library.settings.advanced.database.cleanup.label.error'), 'error', getErrorMessage(e)); } }; @@ -85,8 +86,8 @@ export function LibrarySettings() { request: { error: metadataServerSettingsError, refetch: refetchMetadataServerSettings }, } = useMetadataServerSettings(); - const setSettingValue = createUpdateMetadataServerSettings(() => - makeToast(t('search.error.label.failed_to_save_settings'), 'warning'), + const setSettingValue = createUpdateMetadataServerSettings((e) => + makeToast(t('search.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)), ); const loading = serverSettings.loading || areMetadataServerSettingsLoading || categories.loading; diff --git a/src/modules/manga/components/TrackMangaButton.tsx b/src/modules/manga/components/TrackMangaButton.tsx index 594f796d..289c4cb6 100644 --- a/src/modules/manga/components/TrackMangaButton.tsx +++ b/src/modules/manga/components/TrackMangaButton.tsx @@ -35,7 +35,7 @@ export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick const handleClick = (openPopup: () => void) => { if (trackerList.error) { - makeToast(t('tracking.error.label.could_not_load_track_info'), 'error'); + makeToast(t('tracking.error.label.could_not_load_track_info'), 'error', trackerList.error?.toString()); return; } diff --git a/src/modules/manga/hooks/useManageMangaLibraryState.tsx b/src/modules/manga/hooks/useManageMangaLibraryState.tsx index 93e38fd4..4a520b06 100644 --- a/src/modules/manga/hooks/useManageMangaLibraryState.tsx +++ b/src/modules/manga/hooks/useManageMangaLibraryState.tsx @@ -21,6 +21,7 @@ import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx'; import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables, MangaType } from '@/lib/graphql/generated/graphql.ts'; import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const useManageMangaLibraryState = ( manga: Pick & Partial>, @@ -44,8 +45,8 @@ export const useManageMangaLibraryState = ( }) .response.then(() => makeToast(t('library.info.label.added_to_library'), 'success')) .then(() => setIsInLibrary(true)) - .catch(() => { - makeToast(t('library.error.label.add_to_library'), 'error'); + .catch((e) => { + makeToast(t('library.error.label.add_to_library'), 'error', getErrorMessage(e)); }); }, [manga.id], @@ -86,7 +87,7 @@ export const useManageMangaLibraryState = ( showAddToLibraryCategorySelectDialog = (await getMetadataServerSettings()) .showAddToLibraryCategorySelectDialog; } catch (e) { - makeToast(t('global.error.label.failed_to_load_data'), 'error'); + makeToast(t('global.error.label.failed_to_load_data'), 'error', getErrorMessage(e)); return; } @@ -101,7 +102,7 @@ export const useManageMangaLibraryState = ( GetCategoriesBaseQueryVariables >(GET_CATEGORIES_BASE).response; } catch (e) { - makeToast(t('category.error.label.request_failure'), 'error'); + makeToast(t('category.error.label.request_failure'), 'error', getErrorMessage(e)); return; } const userCreatedCategories = Categories.getUserCreated(categories.data.categories.nodes); diff --git a/src/modules/manga/services/Mangas.ts b/src/modules/manga/services/Mangas.ts index f34eba4a..d2c0ef53 100644 --- a/src/modules/manga/services/Mangas.ts +++ b/src/modules/manga/services/Mangas.ts @@ -34,6 +34,7 @@ import { MigrateMode, } from '@/modules/manga/Manga.types.ts'; import { actionToTranslationKey } from '@/modules/manga/Manga.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type MangaToMigrate = NonNullable; type MangaToMigrateTo = NonNullable['manga']; @@ -481,7 +482,11 @@ export class Mangas { await fnToExecute(); makeToast(translate(actionToTranslationKey[action].success, { count: itemCount }), 'success'); } catch (e) { - makeToast(translate(actionToTranslationKey[action].error, { count: itemCount }), 'error'); + makeToast( + translate(actionToTranslationKey[action].error, { count: itemCount }), + 'error', + getErrorMessage(e), + ); throw e; } } diff --git a/src/modules/migration/screens/Migration.tsx b/src/modules/migration/screens/Migration.tsx index 08e74a72..61491692 100644 --- a/src/modules/migration/screens/Migration.tsx +++ b/src/modules/migration/screens/Migration.tsx @@ -30,6 +30,7 @@ import { } from '@/modules/settings/services/ServerSettingsMetadata.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const getMigratableSources = ( mangas: TMigratableSourcesResult | undefined, @@ -80,8 +81,8 @@ export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => { const { settings: { migrateSortSettings }, } = useMetadataServerSettings(); - const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); const { sortBy, sortOrder } = migrateSortSettings; diff --git a/src/modules/reader/services/ReaderService.ts b/src/modules/reader/services/ReaderService.ts index e929a2da..1f1e4c49 100644 --- a/src/modules/reader/services/ReaderService.ts +++ b/src/modules/reader/services/ReaderService.ts @@ -46,6 +46,7 @@ import { import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { Queue } from '@/lib/Queue.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const DIRECTION_TO_INVERTED: Record = { ltr: 'rtl', @@ -279,8 +280,12 @@ export class ReaderService { } if (commit) { - updateReaderSettings(manga, setting, value, isGlobal, profile).catch(() => - makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error'), + updateReaderSettings(manga, setting, value, isGlobal, profile).catch((e) => + makeToast( + translate('reader.settings.error.label.failed_to_save_settings'), + 'error', + getErrorMessage(e), + ), ); } } @@ -309,8 +314,8 @@ export class ReaderService { const deleteSetting = isGlobalSetting ? () => requestManager.deleteGlobalMeta(key).response : () => requestManager.deleteMangaMeta(manga.id, key).response; - deleteSetting().catch(() => - makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error'), + deleteSetting().catch((e) => + makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)), ); } diff --git a/src/modules/settings/components/globalUpdate/GlobalUpdateSettings.tsx b/src/modules/settings/components/globalUpdate/GlobalUpdateSettings.tsx index e9ee7aca..4dd93ee6 100644 --- a/src/modules/settings/components/globalUpdate/GlobalUpdateSettings.tsx +++ b/src/modules/settings/components/globalUpdate/GlobalUpdateSettings.tsx @@ -21,6 +21,7 @@ import { GlobalUpdateSettingsInterval } from '@/modules/settings/components/glob import { requestManager } from '@/lib/requests/RequestManager.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type LibrarySettingsType = Pick; @@ -42,8 +43,8 @@ export const GlobalUpdateSettings = ({ ) => { try { await mutateSettings({ variables: { input: { settings: { [setting]: value } } } }); - } catch (error) { - makeToast(t('global.error.label.failed_to_save_changes'), 'error'); + } catch (e) { + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)); } }; diff --git a/src/modules/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx b/src/modules/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx index 69ded783..d61b14d7 100644 --- a/src/modules/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx +++ b/src/modules/settings/components/globalUpdate/GlobalUpdateSettingsEntries.tsx @@ -22,6 +22,7 @@ import { CheckboxContainer } from '@/modules/core/components/inputs/CheckboxCont import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { TranslationKey } from '@/Base.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type GlobalUpdateSkipEntriesSettings = Pick< ServerSettings, @@ -90,8 +91,8 @@ export const GlobalUpdateSettingsEntries = ({ serverSettings }: { serverSettings try { await mutateSettings({ variables: { input: { settings: dialogSettings } } }); - } catch (error) { - makeToast(t('global.error.label.failed_to_save_changes'), 'error'); + } catch (e) { + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)); } }; diff --git a/src/modules/settings/components/webUI/WebUIUpdateIntervalSetting.tsx b/src/modules/settings/components/webUI/WebUIUpdateIntervalSetting.tsx index 76f6d9cd..3d880ceb 100644 --- a/src/modules/settings/components/webUI/WebUIUpdateIntervalSetting.tsx +++ b/src/modules/settings/components/webUI/WebUIUpdateIntervalSetting.tsx @@ -17,6 +17,7 @@ import { NumberSetting } from '@/modules/core/components/settings/NumberSetting. import { getPersistedServerSetting, usePersistedValue } from '@/modules/core/hooks/usePersistedValue.tsx'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const DEFAULT_VALUE = 23; const MIN_VALUE = 1; @@ -45,8 +46,8 @@ export const WebUIUpdateIntervalSetting = ({ persistUpdateCheckInterval( webUIUpdateCheckInterval === 0 ? currentUpdateCheckInterval : webUIUpdateCheckInterval, ); - mutateSettings({ variables: { input: { settings: { webUIUpdateCheckInterval } } } }).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + mutateSettings({ variables: { input: { settings: { webUIUpdateCheckInterval } } } }).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }, [currentUpdateCheckInterval], diff --git a/src/modules/settings/screens/Appearance.tsx b/src/modules/settings/screens/Appearance.tsx index 934451e3..f93a3461 100644 --- a/src/modules/settings/screens/Appearance.tsx +++ b/src/modules/settings/screens/Appearance.tsx @@ -34,6 +34,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const Appearance = () => { const { t, i18n } = useTranslation(); @@ -54,8 +55,8 @@ export const Appearance = () => { settings, request: { loading, error, refetch }, } = useMetadataServerSettings(); - const updateMetadataSetting = createUpdateMetadataServerSettings(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + const updateMetadataSetting = createUpdateMetadataServerSettings((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); const isDarkMode = diff --git a/src/modules/settings/screens/ServerSettings.tsx b/src/modules/settings/screens/ServerSettings.tsx index 11ac08f0..2611f43d 100644 --- a/src/modules/settings/screens/ServerSettings.tsx +++ b/src/modules/settings/screens/ServerSettings.tsx @@ -31,6 +31,7 @@ import { import { makeToast } from '@/modules/core/utils/Toast.ts'; import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts'; import { ServerSettings as GqlServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type ServerSettingsType = Pick< GqlServerSettings, @@ -112,7 +113,7 @@ export const ServerSettings = () => { } = useMetadataServerSettings(); const updateMetadataServerSettings = createUpdateMetadataServerSettings< keyof Pick - >(() => makeToast(t('global.error.label.failed_to_save_changes'), 'error')); + >((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e))); const { data, @@ -136,8 +137,8 @@ export const ServerSettings = () => { setting: Setting, value: ServerSettingsType[Setting], ) => { - mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }; diff --git a/src/modules/settings/screens/Settings.tsx b/src/modules/settings/screens/Settings.tsx index 69739b48..aab775f4 100644 --- a/src/modules/settings/screens/Settings.tsx +++ b/src/modules/settings/screens/Settings.tsx @@ -30,6 +30,7 @@ import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.t import { requestManager } from '@/lib/requests/RequestManager.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export function Settings() { const { t } = useTranslation(); @@ -52,7 +53,7 @@ export function Settings() { await triggerClearServerCache(); makeToast(t('settings.clear_cache.label.success'), 'success'); } catch (e) { - makeToast(t('settings.clear_cache.label.failure'), 'error'); + makeToast(t('settings.clear_cache.label.failure'), 'error', getErrorMessage(e)); } }; diff --git a/src/modules/settings/screens/WebUISettings.tsx b/src/modules/settings/screens/WebUISettings.tsx index 65e71b3c..d9ca3851 100644 --- a/src/modules/settings/screens/WebUISettings.tsx +++ b/src/modules/settings/screens/WebUISettings.tsx @@ -32,6 +32,7 @@ import { import { makeToast } from '@/modules/core/utils/Toast.ts'; import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; type WebUISettingsType = Pick< ServerSettings, @@ -138,7 +139,7 @@ export const WebUISettings = () => { } = useMetadataServerSettings(); const updateMetadataServerSettings = createUpdateMetadataServerSettings< keyof Pick - >(() => makeToast(t('global.error.label.failed_to_save_changes'), 'error')); + >((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e))); const { data, @@ -158,8 +159,8 @@ export const WebUISettings = () => { requestManager.graphQLClient.client.cache.evict({ fieldName: 'checkForWebUIUpdate' }); } - mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); }; diff --git a/src/modules/source/screens/SourceConfigure.tsx b/src/modules/source/screens/SourceConfigure.tsx index 396b1493..20f88207 100644 --- a/src/modules/source/screens/SourceConfigure.tsx +++ b/src/modules/source/screens/SourceConfigure.tsx @@ -27,6 +27,7 @@ import { GetCategoriesSettingsQueryVariables, GetSourceSettingsQuery } from '@/l import { GET_SOURCE_SETTINGS } from '@/lib/graphql/queries/SourceQuery.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { PreferenceProps } from '@/modules/source/Source.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; function getPrefComponent(type: string) { switch (type) { @@ -73,7 +74,9 @@ export function SourceConfigure() { (type, value) => { requestManager .setSourcePreferences(sourceId, { position, [type]: value }) - .response.catch(() => makeToast(t('global.error.label.failed_to_save_changes'), 'error')); + .response.catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), + ); }; if (loading) { diff --git a/src/modules/source/screens/SourceMangas.tsx b/src/modules/source/screens/SourceMangas.tsx index bcc2cf34..a97c75c3 100644 --- a/src/modules/source/screens/SourceMangas.tsx +++ b/src/modules/source/screens/SourceMangas.tsx @@ -52,6 +52,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { GridLayout } from '@/modules/core/Core.types.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const DEFAULT_SOURCE: Pick = { id: '-1' }; @@ -290,8 +291,8 @@ export function SourceMangas() { const filters = source?.filters ?? []; const { savedSearches = {} } = useGetSourceMetadata(source ?? DEFAULT_SOURCE); - const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(source ?? { id: '-1' }, () => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(source ?? { id: '-1' }, (e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); const selectSavedSearch = useCallback( diff --git a/src/modules/theme/components/CreateThemeDialog.tsx b/src/modules/theme/components/CreateThemeDialog.tsx index 51aa36f9..0e471bbd 100644 --- a/src/modules/theme/components/CreateThemeDialog.tsx +++ b/src/modules/theme/components/CreateThemeDialog.tsx @@ -19,7 +19,7 @@ import DialogActions from '@mui/material/DialogActions'; import TextField from '@mui/material/TextField'; import Link from '@mui/material/Link'; import { jsonrepair } from 'jsonrepair'; -import { jsonSaveParse } from '@/lib/HelperFunctions.ts'; +import { getErrorMessage, jsonSaveParse } from '@/lib/HelperFunctions.ts'; import { AppTheme, isThemeNameUnique } from '@/modules/theme/services/AppThemes.ts'; import { createUpdateMetadataServerSettings, @@ -203,10 +203,11 @@ export const ThemeCreationDialog = ({ ); bindDialogProps.onClose(e); }) - .catch(() => + .catch((updateError) => makeToast( t(dialogModeToTranslationKey[mode].failure, { theme: theme.getName() }), 'error', + getErrorMessage(updateError), ), ) .finally(() => setIsCreating(false)); diff --git a/src/modules/theme/components/ThemeList.tsx b/src/modules/theme/components/ThemeList.tsx index 79149be2..2122e3e5 100644 --- a/src/modules/theme/components/ThemeList.tsx +++ b/src/modules/theme/components/ThemeList.tsx @@ -21,6 +21,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts' import { CreateThemeButton } from '@/modules/theme/components/CreateThemeButton.tsx'; import { ThemePreview } from '@/modules/theme/components/ThemePreview.tsx'; import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const ThemeList = () => { const { t } = useTranslation(); @@ -80,10 +81,11 @@ export const ThemeList = () => { 'success', ), ) - .catch(() => + .catch((e) => makeToast( t('settings.appearance.theme.delete.failure', { theme: theme.getName() }), 'error', + getErrorMessage(e), ), ); }} diff --git a/src/modules/theme/components/ThemePreview.tsx b/src/modules/theme/components/ThemePreview.tsx index ce933989..88c63860 100644 --- a/src/modules/theme/components/ThemePreview.tsx +++ b/src/modules/theme/components/ThemePreview.tsx @@ -25,6 +25,7 @@ import { createTheme } from '@/modules/theme/services/ThemeCreator.ts'; import { ThemeCreationDialog } from '@/modules/theme/components/CreateThemeDialog.tsx'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const ThemePreviewBadge = styled(Box)(() => ({ width: '15px', @@ -93,7 +94,13 @@ export const ThemePreview = ({ theme, onDelete }: { theme: AppTheme; onDelete: ( makeToast(t('settings.appearance.theme.select.fonts.loading'), 'info'); loadThemeFonts(theme.muiTheme) .then(() => setAppTheme(theme.id)) - .catch(() => makeToast(t('settings.appearance.theme.select.fonts.error'), 'error')); + .catch((e) => + makeToast( + t('settings.appearance.theme.select.fonts.error'), + 'error', + getErrorMessage(e), + ), + ); }} > diff --git a/src/modules/tracker/components/TrackManga.tsx b/src/modules/tracker/components/TrackManga.tsx index 7d3e8f66..560b42f7 100644 --- a/src/modules/tracker/components/TrackManga.tsx +++ b/src/modules/tracker/components/TrackManga.tsx @@ -23,6 +23,7 @@ import { GET_TRACKERS_BIND } from '@/lib/graphql/queries/TrackerQuery.ts'; import { GET_MANGA_TRACK_RECORDS } from '@/lib/graphql/queries/MangaQuery.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => { if (id === searchModeForTracker) { @@ -78,7 +79,7 @@ export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick requestManager.fetchTrackBind(trackRecord.id).response), - ).catch(() => makeToast(t('tracking.error.label.could_not_fetch_track_info'), 'error')); + ).catch((e) => makeToast(t('tracking.error.label.could_not_fetch_track_info'), 'error', getErrorMessage(e))); }, [mangaTrackRecords]); const trackerComponents = useMemo( diff --git a/src/modules/tracker/components/TrackerSearch.tsx b/src/modules/tracker/components/TrackerSearch.tsx index 27ce842c..b03498a5 100644 --- a/src/modules/tracker/components/TrackerSearch.tsx +++ b/src/modules/tracker/components/TrackerSearch.tsx @@ -34,6 +34,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts' import { MangaType } from '@/lib/graphql/generated/graphql.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const TrackerSearch = ({ manga, @@ -90,7 +91,7 @@ export const TrackerSearch = ({ makeToast(t('manga.action.track.add.label.success'), 'success'); closeSearchMode(); }) - .catch(() => makeToast(t('manga.action.track.add.label.error'), 'error')); + .catch((e) => makeToast(t('manga.action.track.add.label.error'), 'error', getErrorMessage(e))); }; return ( diff --git a/src/modules/tracker/components/cards/SettingsTrackerCard.tsx b/src/modules/tracker/components/cards/SettingsTrackerCard.tsx index 7183c553..e7249287 100644 --- a/src/modules/tracker/components/cards/SettingsTrackerCard.tsx +++ b/src/modules/tracker/components/cards/SettingsTrackerCard.tsx @@ -25,6 +25,7 @@ import { PasswordTextField } from '@/modules/core/components/inputs/PasswordText import { makeToast } from '@/modules/core/utils/Toast.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { Trackers, TTrackerSearch } from '@/modules/tracker/services/Trackers.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => { const { t } = useTranslation(); @@ -42,7 +43,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => try { await logoutFromTracker({ variables: { trackerId: tracker.id } }); } catch (e) { - makeToast(t('tracking.action.logout.label.failure', { name: tracker.name }), 'error'); + makeToast(t('tracking.action.logout.label.failure', { name: tracker.name }), 'error', getErrorMessage(e)); } }; @@ -62,7 +63,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => try { await loginTrackerCredentials({ variables: { input: { trackerId: tracker.id, username, password } } }); } catch (e) { - makeToast(t('tracking.action.login.label.failure', { name: tracker.name }), 'error'); + makeToast(t('tracking.action.login.label.failure', { name: tracker.name }), 'error', getErrorMessage(e)); } }; diff --git a/src/modules/tracker/components/cards/TrackerActiveCard.tsx b/src/modules/tracker/components/cards/TrackerActiveCard.tsx index fe1ace61..8ee69a19 100644 --- a/src/modules/tracker/components/cards/TrackerActiveCard.tsx +++ b/src/modules/tracker/components/cards/TrackerActiveCard.tsx @@ -40,6 +40,7 @@ import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines import { SelectSetting, SelectSettingValue } from '@/modules/core/components/settings/SelectSetting.tsx'; import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { TrackRecordType } from '@/lib/graphql/generated/graphql.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const TrackerActiveLink = ({ children, url }: { children: React.ReactNode; url: string }) => ( @@ -69,7 +70,7 @@ const TrackerActiveRemoveBind = ({ requestManager .unbindTracker(trackerRecordId, removeRemoteTracking) .response.then(() => makeToast(t('manga.action.track.remove.label.success'), 'success')) - .catch(() => makeToast(t('manga.action.track.remove.label.error'), 'error')); + .catch((e) => makeToast(t('manga.action.track.remove.label.error'), 'error', getErrorMessage(e))); }; return ( @@ -250,7 +251,9 @@ export const TrackerActiveCard = ({ const updateTrackerBind = (patch: Parameters[1]) => { requestManager .updateTrackerBind(trackRecord.id, patch) - .response.catch(() => makeToast(t('global.error.label.failed_to_save_changes'), 'error')); + .response.catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), + ); }; return ( diff --git a/src/modules/tracker/screens/TrackerOAuthLogin.tsx b/src/modules/tracker/screens/TrackerOAuthLogin.tsx index 189c3e77..b48a2ff3 100644 --- a/src/modules/tracker/screens/TrackerOAuthLogin.tsx +++ b/src/modules/tracker/screens/TrackerOAuthLogin.tsx @@ -12,6 +12,7 @@ import { useNavigate } from 'react-router-dom'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const TrackerOAuthLogin = () => { const { t } = useTranslation(); @@ -36,7 +37,7 @@ export const TrackerOAuthLogin = () => { }, }); } catch (e) { - makeToast(t('tracking.action.login.label.failure', { name: trackerName }), 'error'); + makeToast(t('tracking.action.login.label.failure', { name: trackerName }), 'error', getErrorMessage(e)); } navigate(AppRoutes.tracker.path, { replace: true }); diff --git a/src/modules/tracker/screens/TrackingSettings.tsx b/src/modules/tracker/screens/TrackingSettings.tsx index 397bb85a..f9ee8779 100644 --- a/src/modules/tracker/screens/TrackingSettings.tsx +++ b/src/modules/tracker/screens/TrackingSettings.tsx @@ -28,6 +28,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts' import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts'; import { GetTrackersSettingsQuery } from '@/lib/graphql/generated/graphql.ts'; import { MetadataTrackingSettings } from '@/modules/tracker/Tracker.types.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; export const TrackingSettings = () => { const { t } = useTranslation(); @@ -42,8 +43,8 @@ export const TrackingSettings = () => { loading: areMetadataServerSettingsLoading, request: { error: metadataServerSettingsError, refetch: refetchServerMetadataSettings }, } = useMetadataServerSettings(); - const updateTrackingSettings = createUpdateMetadataServerSettings(() => - makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + const updateTrackingSettings = createUpdateMetadataServerSettings((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), ); const { diff --git a/src/modules/updates/screens/Updates.tsx b/src/modules/updates/screens/Updates.tsx index af802ec9..83a76c58 100644 --- a/src/modules/updates/screens/Updates.tsx +++ b/src/modules/updates/screens/Updates.tsx @@ -38,6 +38,7 @@ import { ChapterIdInfo, ChapterMangaInfo } from '@/modules/chapter/services/Chap import { makeToast } from '@/modules/core/utils/Toast.ts'; import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { getErrorMessage } from '@/lib/HelperFunctions.ts'; const groupByDate = (updates: Pick[]): [date: string, items: number][] => { if (!updates.length) { @@ -115,14 +116,16 @@ export const Updates: React.FC = () => { try { await requestManager.addChapterToDownloadQueue(chapter.id).response; } catch (e) { - makeToast(t('download.queue.error.label.failed_to_remove'), 'error'); + makeToast(t('download.queue.error.label.failed_to_remove'), 'error', getErrorMessage(e)); } }; const downloadChapter = (chapter: ChapterIdInfo) => { requestManager .addChapterToDownloadQueue(chapter.id) - .response.catch(() => makeToast(t('global.error.label.failed_to_save_changes'), 'error')); + .response.catch((e) => + makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)), + ); }; const loadMore = useCallback(() => {