Include actual error in snackbar

This commit is contained in:
schroda
2024-12-20 17:24:40 +01:00
parent 91a1f75de6
commit 3949363474
44 changed files with 292 additions and 91 deletions

View File

@@ -408,6 +408,7 @@
"disabled": "Disabled", "disabled": "Disabled",
"discord": "Discord", "discord": "Discord",
"display": "Display", "display": "Display",
"error": "Error",
"filter": "Filter", "filter": "Filter",
"finished": "Finished", "finished": "Finished",
"footnote": "{{value}}<0>$t(global.label.asterisk)</0>", "footnote": "{{value}}<0>$t(global.label.asterisk)</0>",
@@ -415,6 +416,7 @@
"github": "GitHub", "github": "GitHub",
"hidden": "Hidden", "hidden": "Hidden",
"horizontal": "Horizontal", "horizontal": "Horizontal",
"info": "Information",
"left": "Left", "left": "Left",
"links": "Links", "links": "Links",
"load_in_progress": "Still loading required data…", "load_in_progress": "Still loading required data…",
@@ -435,10 +437,12 @@
"sort": "Sort", "sort": "Sort",
"standard": "Standard", "standard": "Standard",
"started": "Started", "started": "Started",
"success": "Success",
"type": "Type", "type": "Type",
"unknown": "Unknown", "unknown": "Unknown",
"username": "Username", "username": "Username",
"vertical": "Vertical" "vertical": "Vertical",
"warning": "Warning"
}, },
"language": { "language": {
"label": { "label": {

View File

@@ -6,6 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
import { ApolloError } from '@apollo/client/errors';
export const jsonSaveParse = <T = any>(...args: Parameters<typeof JSON.parse>): T | null => { export const jsonSaveParse = <T = any>(...args: Parameters<typeof JSON.parse>): T | null => {
try { try {
return JSON.parse(...args); return JSON.parse(...args);
@@ -13,3 +15,19 @@ export const jsonSaveParse = <T = any>(...args: Parameters<typeof JSON.parse>):
return null; 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}`;
};

View File

@@ -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 { VersionUpdateInfoDialog } from '@/modules/app-updates/components/VersionUpdateInfoDialog.tsx';
import { useUpdateChecker } from '@/modules/app-updates/hooks/useUpdateChecker.tsx'; import { useUpdateChecker } from '@/modules/app-updates/hooks/useUpdateChecker.tsx';
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const disabledUpdateCheck = () => Promise.resolve(); const disabledUpdateCheck = () => Promise.resolve();
@@ -136,7 +137,9 @@ export const WebUIUpdateChecker = () => {
onAction={() => onAction={() =>
requestManager requestManager
.updateWebUI() .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={ actionTitle={
isUpdateInProgress isUpdateInProgress

View File

@@ -36,6 +36,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type BackupSettingsType = Pick<ServerSettings, 'backupPath' | 'backupTime' | 'backupInterval' | 'backupTTL'>; type BackupSettingsType = Pick<ServerSettings, 'backupPath' | 'backupTime' | 'backupInterval' | 'backupTTL'>;
@@ -101,8 +102,8 @@ export function Backup() {
setting: Setting, setting: Setting,
value: BackupSettingsType[Setting], value: BackupSettingsType[Setting],
) => { ) => {
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}; };
@@ -152,7 +153,7 @@ export function Backup() {
return true; return true;
} catch (e) { } 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(); resetBackupState();
} }
@@ -167,7 +168,7 @@ export function Backup() {
backupRestoreId = response.data?.restoreBackup.id; backupRestoreId = response.data?.restoreBackup.id;
setTriggerReRender(Date.now()); setTriggerReRender(Date.now());
} catch (e) { } 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 { } finally {
resetBackupState(); resetBackupState();
} }

View File

@@ -28,6 +28,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { MetadataBrowseSettings } from '@/modules/browse/Browse.types.ts'; import { MetadataBrowseSettings } from '@/modules/browse/Browse.types.ts';
import { ServerSettings as GqlServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings as GqlServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type ExtensionsSettings = Pick<GqlServerSettings, 'maxSourcesInParallel' | 'localSourcePath' | 'extensionRepos'>; type ExtensionsSettings = Pick<GqlServerSettings, 'maxSourcesInParallel' | 'localSourcePath' | 'extensionRepos'>;
@@ -57,16 +58,16 @@ export const BrowseSettings = () => {
setting: Setting, setting: Setting,
value: ExtensionsSettings[Setting], value: ExtensionsSettings[Setting],
) => { ) => {
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}; };
const { const {
settings: { hideLibraryEntries }, settings: { hideLibraryEntries },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const updateMetadataServerSettings = createUpdateMetadataServerSettings<keyof MetadataBrowseSettings>(() => const updateMetadataServerSettings = createUpdateMetadataServerSettings<keyof MetadataBrowseSettings>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
if (loading) { if (loading) {

View File

@@ -28,6 +28,7 @@ import {
CategoryNameInfo, CategoryNameInfo,
CategoryUpdateInclusionInfo, CategoryUpdateInclusionInfo,
} from '@/modules/category/Category.types.ts'; } from '@/modules/category/Category.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type CategoryType = CategoryIdInfo & CategoryNameInfo & CategoryUpdateInclusionInfo & CategoryDownloadInclusionInfo; type CategoryType = CategoryIdInfo & CategoryNameInfo & CategoryUpdateInclusionInfo & CategoryDownloadInclusionInfo;
@@ -139,8 +140,8 @@ export const CategoriesInclusionSetting = ({
await Promise.all(categoriesToUpdate.map((category) => updateCategory(category))); await Promise.all(categoriesToUpdate.map((category) => updateCategory(category)));
// TODO - update cache immediately // TODO - update cache immediately
// mutate(categoriesEndpoint, [...dialogCategories], { revalidate: false }); // mutate(categoriesEndpoint, [...dialogCategories], { revalidate: false });
} catch (error) { } catch (e) {
makeToast(t('global.error.label.failed_to_save_changes'), 'error'); makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e));
// mutate(categoriesEndpoint, [...categories]); // mutate(categoriesEndpoint, [...categories]);
} }
}; };

View File

@@ -34,6 +34,7 @@ import {
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts'; import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
import { GET_MANGA_CATEGORIES } from '@/lib/graphql/queries/MangaQuery.ts'; import { GET_MANGA_CATEGORIES } from '@/lib/graphql/queries/MangaQuery.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type BaseProps = { type BaseProps = {
open: boolean; open: boolean;
@@ -150,8 +151,8 @@ export function CategorySelect(props: CategorySelectProps) {
onClose(true, addToCategories, removeFromCategories); onClose(true, addToCategories, removeFromCategories);
if (doNotShowAddToLibraryDialogAgain) { if (doNotShowAddToLibraryDialogAgain) {
updateMetadataServerSettings('showAddToLibraryCategorySelectDialog', false).catch(() => updateMetadataServerSettings('showAddToLibraryCategorySelectDialog', false).catch((e) =>
makeToast(t('search.error.label.failed_to_save_settings'), 'error'), makeToast(t('search.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)),
); );
} }

View File

@@ -23,6 +23,7 @@ import { DirectionOffset, TranslationKey } from '@/Base.types.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts'; import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.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'; export type ChapterAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread';
@@ -279,7 +280,11 @@ export class Chapters {
await fnToExecute(); await fnToExecute();
makeToast(translate(actionToTranslationKey[action].success, { count: itemCount }), 'success'); makeToast(translate(actionToTranslationKey[action].success, { count: itemCount }), 'success');
} catch (e) { } catch (e) {
makeToast(translate(actionToTranslationKey[action].error, { count: itemCount }), 'error'); makeToast(
translate(actionToTranslationKey[action].error, { count: itemCount }),
'error',
getErrorMessage(e),
);
throw e; throw e;
} }
} }

View File

@@ -25,6 +25,7 @@ import { dateTimeFormatter } from '@/util/DateHelper.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
import { CategoryIdInfo } from '@/modules/category/Category.types.ts'; import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const calcProgress = (status: UpdaterSubscription['updateStatusChanged'] | undefined) => { const calcProgress = (status: UpdaterSubscription['updateStatusChanged'] | undefined) => {
if (!status) { if (!status) {
@@ -93,7 +94,7 @@ export function UpdateChecker({
reFetchLastTimestamp().catch(defaultPromiseErrorHandler('UpdateChecker::reFetchLastTimestamp')); reFetchLastTimestamp().catch(defaultPromiseErrorHandler('UpdateChecker::reFetchLastTimestamp'));
} catch (e) { } catch (e) {
lastRunningState = false; 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 { try {
await requestManager.resetGlobalUpdate(); await requestManager.resetGlobalUpdate();
} catch (e) { } catch (e) {
makeToast(t('library.error.label.stop_global_update'), 'error'); makeToast(t('library.error.label.stop_global_update'), 'error', getErrorMessage(e));
} }
}; };

View File

@@ -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<VariantType, TranslationKey> = {
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<HTMLDivElement>,
) => {
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 (
<SnackbarContent ref={ref}>
<Alert severity={severity} action={finalAction}>
<AlertTitle>{message}</AlertTitle>
{actualDescription}{' '}
{isDescriptionTooLong ? (
<Button
onClick={() => {
awaitConfirmation({
title:
typeof message === 'string'
? message
: t(SNACKBAR_VARIANT_TO_TRANSLATION_KEY[variant]),
message: description ?? '',
actions: {
cancel: { show: false },
confirm: { title: t('global.label.close') },
},
}).catch(
defaultPromiseErrorHandler(
`SnackbarWidthDescription: ${id} - ${message} - ${description}`,
),
);
}}
size="small"
>
{t('global.button.show_more')}
</Button>
) : (
''
)}
</Alert>
</SnackbarContent>
);
},
),
);

View File

@@ -26,6 +26,7 @@ import { useMetadataServerSettings } from '@/modules/settings/services/ServerSet
import { ReaderContextProvider } from '@/modules/reader/contexts/ReaderContextProvider.tsx'; import { ReaderContextProvider } from '@/modules/reader/contexts/ReaderContextProvider.tsx';
import { DIRECTION_TO_CACHE } from '@/modules/theme/ThemeDirectionCache.ts'; import { DIRECTION_TO_CACHE } from '@/modules/theme/ThemeDirectionCache.ts';
import { AppHotkeysProvider } from '@/modules/hotkeys/contexts/AppHotkeysProvider.tsx'; import { AppHotkeysProvider } from '@/modules/hotkeys/contexts/AppHotkeysProvider.tsx';
import { SnackbarWithDescription } from '@/modules/core/components/snackbar/SnackbarWithDescription.tsx';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
@@ -84,7 +85,15 @@ export const AppContext: React.FC<Props> = ({ children }) => {
<LibraryOptionsContextProvider> <LibraryOptionsContextProvider>
<NavBarContextProvider> <NavBarContextProvider>
<ActiveDeviceContextProvider> <ActiveDeviceContextProvider>
<SnackbarProvider> <SnackbarProvider
Components={{
default: SnackbarWithDescription,
info: SnackbarWithDescription,
success: SnackbarWithDescription,
warning: SnackbarWithDescription,
error: SnackbarWithDescription,
}}
>
<ReaderContextProvider> <ReaderContextProvider>
<AppHotkeysProvider>{children}</AppHotkeysProvider> <AppHotkeysProvider>{children}</AppHotkeysProvider>
</ReaderContextProvider> </ReaderContextProvider>

View File

@@ -8,12 +8,21 @@
import { enqueueSnackbar, OptionsObject, SnackbarKey } from 'notistack'; import { enqueueSnackbar, OptionsObject, SnackbarKey } from 'notistack';
export function makeToast(message: string, severity?: OptionsObject['variant']): SnackbarKey; export function makeToast(message: string, severity?: OptionsObject['variant'], description?: string): SnackbarKey;
export function makeToast(message: string, options?: OptionsObject): SnackbarKey; export function makeToast(message: string, options?: OptionsObject, description?: string): SnackbarKey;
export function makeToast(message: string, options: OptionsObject['variant'] | OptionsObject = 'default'): SnackbarKey { export function makeToast(
if (typeof options === 'string') { message: string,
return enqueueSnackbar(message, { variant: options }); 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,
});
} }

View File

@@ -26,6 +26,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { DEFAULT_DEVICE } from '@/modules/device/services/Device.ts'; import { DEFAULT_DEVICE } from '@/modules/device/services/Device.ts';
import { MetadataServerSettingKeys, MetadataServerSettings } from '@/modules/settings/Settings.types.ts'; import { MetadataServerSettingKeys, MetadataServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const DeviceSetting = () => { export const DeviceSetting = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -63,8 +64,8 @@ export const DeviceSetting = () => {
setActiveDevice(DEFAULT_DEVICE); setActiveDevice(DEFAULT_DEVICE);
} }
updateMetadataServerSettings(setting, value).catch(() => updateMetadataServerSettings(setting, value).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}; };

View File

@@ -17,6 +17,7 @@ import { updateMetadataServerSettings } from '@/modules/settings/services/Server
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts'; import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts';
import { MetadataServerSettings } from '@/modules/settings/Settings.types.ts'; import { MetadataServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const MIN_LIMIT = 2; const MIN_LIMIT = 2;
const MAX_LIMIT = 10; const MAX_LIMIT = 10;
@@ -39,8 +40,8 @@ export const DownloadAheadSetting = ({
const updateSetting = (value: MetadataDownloadSettings['downloadAheadLimit']) => { const updateSetting = (value: MetadataDownloadSettings['downloadAheadLimit']) => {
persistDownloadAheadLimit(value === 0 ? currentDownloadAheadLimit : value); persistDownloadAheadLimit(value === 0 ? currentDownloadAheadLimit : value);
updateMetadataServerSettings('downloadAheadLimit', value).catch(() => updateMetadataServerSettings('downloadAheadLimit', value).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}; };

View File

@@ -36,6 +36,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'
import { ChapterDownloadStatus, ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts'; import { ChapterDownloadStatus, ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
import { DownloaderState, DownloadState } from '@/lib/graphql/generated/graphql.ts'; import { DownloaderState, DownloadState } from '@/lib/graphql/generated/graphql.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const HeightPreservingItem = ({ children, ...props }: BoxProps) => ( const HeightPreservingItem = ({ children, ...props }: BoxProps) => (
// the height is necessary to prevent the item container from collapsing, which confuses Virtuoso measurements // 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 { try {
await requestManager.clearDownloads().response; await requestManager.clearDownloads().response;
} catch (e) { } 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 { try {
await requestManager.addChapterToDownloadQueue(chapter.id).response; await requestManager.addChapterToDownloadQueue(chapter.id).response;
} catch (e) { } 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, requestManager.deleteDownloadedChapter(chapter.id).response,
]); ]);
} catch (e) { } 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) { if (!isRunning) {

View File

@@ -32,6 +32,7 @@ import { GetCategoriesSettingsQuery, GetCategoriesSettingsQueryVariables } from
import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts'; import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts';
import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts'; import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts';
import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type DownloadSettingsType = Pick< type DownloadSettingsType = Pick<
ServerSettings, ServerSettings,
@@ -115,13 +116,13 @@ export const DownloadSettings = () => {
setting: Setting, setting: Setting,
value: DownloadSettingsType[Setting], value: DownloadSettingsType[Setting],
) => { ) => {
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}; };
const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataDownloadSettings>(() => const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataDownloadSettings>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
return ( return (

View File

@@ -32,6 +32,7 @@ import {
INSTALLED_STATE_TO_TRANSLATION_KEY_MAP, INSTALLED_STATE_TO_TRANSLATION_KEY_MAP,
} from '@/modules/extension/Extensions.constants.ts'; } from '@/modules/extension/Extensions.constants.ts';
import { getInstalledState } from '@/modules/extension/Extensions.utils.ts'; import { getInstalledState } from '@/modules/extension/Extensions.utils.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
interface IProps { interface IProps {
extension: TExtension; extension: TExtension;
@@ -84,7 +85,11 @@ export function ExtensionCard(props: IProps) {
handleUpdate(); handleUpdate();
} catch (e) { } catch (e) {
setInstalledState(getInstalledState(isInstalled, isObsolete, hasUpdate)); 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),
);
} }
}; };

View File

@@ -40,6 +40,7 @@ import {
import { ExtensionAction, ExtensionGroupState, ExtensionState } from '@/modules/extension/Extensions.types.ts'; 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 { EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP } from '@/modules/extension/Extensions.constants.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const LANGUAGE = 0; const LANGUAGE = 0;
const EXTENSIONS = 1; const EXTENSIONS = 1;
@@ -123,7 +124,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
handleExtensionUpdate(); handleExtensionUpdate();
makeToast(t('extension.label.installed_successfully'), 'success'); 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 { } else {
makeToast(t('global.error.label.invalid_file_type'), 'error'); makeToast(t('global.error.label.invalid_file_type'), 'error');
} }
@@ -263,7 +264,7 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
requestManager requestManager
.updateExtensions(extensionIds, { update: true }) .updateExtensions(extensionIds, { update: true })
.response.then(() => handleExtensionUpdate()) .response.then(() => handleExtensionUpdate())
.catch(() => .catch((e) =>
makeToast( makeToast(
t( t(
EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[ EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[
@@ -271,6 +272,8 @@ export function Extensions({ tabsMenuHeight }: { tabsMenuHeight: number }) {
], ],
{ count: groupedExtensions.length }, { count: groupedExtensions.length },
), ),
'error',
getErrorMessage(e),
), ),
) )
.finally(() => setUpdatingExtensionIds([])); .finally(() => setUpdatingExtensionIds([]));

View File

@@ -29,6 +29,7 @@ import { LibrarySortMode } from '@/modules/library/Library.types.ts';
import { CategoryMetadataInfo } from '@/modules/category/Category.types.ts'; import { CategoryMetadataInfo } from '@/modules/category/Category.types.ts';
import { statusToTranslationKey } from '@/modules/manga/Manga.constants.ts'; import { statusToTranslationKey } from '@/modules/manga/Manga.constants.ts';
import { GridLayout } from '@/modules/core/Core.types.ts'; import { GridLayout } from '@/modules/core/Core.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = { const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = {
filter: 'global.label.filter', filter: 'global.label.filter',
@@ -61,15 +62,15 @@ export const LibraryOptionsPanel = ({
const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []); const loggedInTrackers = Trackers.getLoggedIn(trackerList.data?.trackers.nodes ?? []);
const categoryLibraryOptions = useGetCategoryMetadata(category); const categoryLibraryOptions = useGetCategoryMetadata(category);
const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, () => const updateCategoryLibraryOptions = createUpdateCategoryMetadata(category, (e) =>
makeToast(t('global.error.label.failed_to_save_changes', 'error')), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
const { const {
settings: { showTabSize }, settings: { showTabSize },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const setSettingValue = createUpdateMetadataServerSettings<'showTabSize'>(() => const setSettingValue = createUpdateMetadataServerSettings<'showTabSize'>((e) =>
makeToast(t('search.error.label.failed_to_save_settings'), 'warning'), makeToast(t('search.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)),
); );
return ( return (

View File

@@ -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 { GET_MANGAS_BASE } from '@/lib/graphql/queries/MangaQuery.ts';
import { MetadataLibrarySettings } from '@/modules/library/Library.types.ts'; import { MetadataLibrarySettings } from '@/modules/library/Library.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const removeNonLibraryMangasFromCategories = async (): Promise<void> => { const removeNonLibraryMangasFromCategories = async (): Promise<void> => {
try { try {
@@ -57,7 +58,7 @@ const removeNonLibraryMangasFromCategories = async (): Promise<void> => {
} }
makeToast(translate('library.settings.advanced.database.cleanup.label.success'), 'success'); makeToast(translate('library.settings.advanced.database.cleanup.label.success'), 'success');
} catch (e) { } 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 }, request: { error: metadataServerSettingsError, refetch: refetchMetadataServerSettings },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const setSettingValue = createUpdateMetadataServerSettings<keyof MetadataLibrarySettings>(() => const setSettingValue = createUpdateMetadataServerSettings<keyof MetadataLibrarySettings>((e) =>
makeToast(t('search.error.label.failed_to_save_settings'), 'warning'), makeToast(t('search.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)),
); );
const loading = serverSettings.loading || areMetadataServerSettingsLoading || categories.loading; const loading = serverSettings.loading || areMetadataServerSettingsLoading || categories.loading;

View File

@@ -35,7 +35,7 @@ export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick
const handleClick = (openPopup: () => void) => { const handleClick = (openPopup: () => void) => {
if (trackerList.error) { 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; return;
} }

View File

@@ -21,6 +21,7 @@ import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables, MangaType } from '@/lib/graphql/generated/graphql.ts'; import { GetCategoriesBaseQuery, GetCategoriesBaseQueryVariables, MangaType } from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts'; import { GET_CATEGORIES_BASE } from '@/lib/graphql/queries/CategoryQuery.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const useManageMangaLibraryState = ( export const useManageMangaLibraryState = (
manga: Pick<MangaType, 'id' | 'title'> & Partial<Pick<MangaType, 'inLibrary'>>, manga: Pick<MangaType, 'id' | 'title'> & Partial<Pick<MangaType, 'inLibrary'>>,
@@ -44,8 +45,8 @@ export const useManageMangaLibraryState = (
}) })
.response.then(() => makeToast(t('library.info.label.added_to_library'), 'success')) .response.then(() => makeToast(t('library.info.label.added_to_library'), 'success'))
.then(() => setIsInLibrary(true)) .then(() => setIsInLibrary(true))
.catch(() => { .catch((e) => {
makeToast(t('library.error.label.add_to_library'), 'error'); makeToast(t('library.error.label.add_to_library'), 'error', getErrorMessage(e));
}); });
}, },
[manga.id], [manga.id],
@@ -86,7 +87,7 @@ export const useManageMangaLibraryState = (
showAddToLibraryCategorySelectDialog = (await getMetadataServerSettings()) showAddToLibraryCategorySelectDialog = (await getMetadataServerSettings())
.showAddToLibraryCategorySelectDialog; .showAddToLibraryCategorySelectDialog;
} catch (e) { } 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; return;
} }
@@ -101,7 +102,7 @@ export const useManageMangaLibraryState = (
GetCategoriesBaseQueryVariables GetCategoriesBaseQueryVariables
>(GET_CATEGORIES_BASE).response; >(GET_CATEGORIES_BASE).response;
} catch (e) { } catch (e) {
makeToast(t('category.error.label.request_failure'), 'error'); makeToast(t('category.error.label.request_failure'), 'error', getErrorMessage(e));
return; return;
} }
const userCreatedCategories = Categories.getUserCreated(categories.data.categories.nodes); const userCreatedCategories = Categories.getUserCreated(categories.data.categories.nodes);

View File

@@ -34,6 +34,7 @@ import {
MigrateMode, MigrateMode,
} from '@/modules/manga/Manga.types.ts'; } from '@/modules/manga/Manga.types.ts';
import { actionToTranslationKey } from '@/modules/manga/Manga.constants.ts'; import { actionToTranslationKey } from '@/modules/manga/Manga.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type MangaToMigrate = NonNullable<GetMangaToMigrateQuery['manga']>; type MangaToMigrate = NonNullable<GetMangaToMigrateQuery['manga']>;
type MangaToMigrateTo = NonNullable<GetMangaToMigrateToFetchMutation['fetchManga']>['manga']; type MangaToMigrateTo = NonNullable<GetMangaToMigrateToFetchMutation['fetchManga']>['manga'];
@@ -481,7 +482,11 @@ export class Mangas {
await fnToExecute(); await fnToExecute();
makeToast(translate(actionToTranslationKey[action].success, { count: itemCount }), 'success'); makeToast(translate(actionToTranslationKey[action].success, { count: itemCount }), 'success');
} catch (e) { } catch (e) {
makeToast(translate(actionToTranslationKey[action].error, { count: itemCount }), 'error'); makeToast(
translate(actionToTranslationKey[action].error, { count: itemCount }),
'error',
getErrorMessage(e),
);
throw e; throw e;
} }
} }

View File

@@ -30,6 +30,7 @@ import {
} from '@/modules/settings/services/ServerSettingsMetadata.ts'; } from '@/modules/settings/services/ServerSettingsMetadata.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const getMigratableSources = ( const getMigratableSources = (
mangas: TMigratableSourcesResult | undefined, mangas: TMigratableSourcesResult | undefined,
@@ -80,8 +81,8 @@ export const Migration = ({ tabsMenuHeight }: { tabsMenuHeight: number }) => {
const { const {
settings: { migrateSortSettings }, settings: { migrateSortSettings },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>(() => const updateMetadataServerSettings = createUpdateMetadataServerSettings<'migrateSortSettings'>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
const { sortBy, sortOrder } = migrateSortSettings; const { sortBy, sortOrder } = migrateSortSettings;

View File

@@ -46,6 +46,7 @@ import {
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { Queue } from '@/lib/Queue.ts'; import { Queue } from '@/lib/Queue.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const DIRECTION_TO_INVERTED: Record<Direction, Direction> = { const DIRECTION_TO_INVERTED: Record<Direction, Direction> = {
ltr: 'rtl', ltr: 'rtl',
@@ -279,8 +280,12 @@ export class ReaderService {
} }
if (commit) { if (commit) {
updateReaderSettings(manga, setting, value, isGlobal, profile).catch(() => updateReaderSettings(manga, setting, value, isGlobal, profile).catch((e) =>
makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error'), makeToast(
translate('reader.settings.error.label.failed_to_save_settings'),
'error',
getErrorMessage(e),
),
); );
} }
} }
@@ -309,8 +314,8 @@ export class ReaderService {
const deleteSetting = isGlobalSetting const deleteSetting = isGlobalSetting
? () => requestManager.deleteGlobalMeta(key).response ? () => requestManager.deleteGlobalMeta(key).response
: () => requestManager.deleteMangaMeta(manga.id, key).response; : () => requestManager.deleteMangaMeta(manga.id, key).response;
deleteSetting().catch(() => deleteSetting().catch((e) =>
makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error'), makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error', getErrorMessage(e)),
); );
} }

View File

@@ -21,6 +21,7 @@ import { GlobalUpdateSettingsInterval } from '@/modules/settings/components/glob
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type LibrarySettingsType = Pick<ServerSettings, 'updateMangas'>; type LibrarySettingsType = Pick<ServerSettings, 'updateMangas'>;
@@ -42,8 +43,8 @@ export const GlobalUpdateSettings = ({
) => { ) => {
try { try {
await mutateSettings({ variables: { input: { settings: { [setting]: value } } } }); await mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
} catch (error) { } catch (e) {
makeToast(t('global.error.label.failed_to_save_changes'), 'error'); makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e));
} }
}; };

View File

@@ -22,6 +22,7 @@ import { CheckboxContainer } from '@/modules/core/components/inputs/CheckboxCont
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { TranslationKey } from '@/Base.types.ts'; import { TranslationKey } from '@/Base.types.ts';
import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type GlobalUpdateSkipEntriesSettings = Pick< type GlobalUpdateSkipEntriesSettings = Pick<
ServerSettings, ServerSettings,
@@ -90,8 +91,8 @@ export const GlobalUpdateSettingsEntries = ({ serverSettings }: { serverSettings
try { try {
await mutateSettings({ variables: { input: { settings: dialogSettings } } }); await mutateSettings({ variables: { input: { settings: dialogSettings } } });
} catch (error) { } catch (e) {
makeToast(t('global.error.label.failed_to_save_changes'), 'error'); makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e));
} }
}; };

View File

@@ -17,6 +17,7 @@ import { NumberSetting } from '@/modules/core/components/settings/NumberSetting.
import { getPersistedServerSetting, usePersistedValue } from '@/modules/core/hooks/usePersistedValue.tsx'; import { getPersistedServerSetting, usePersistedValue } from '@/modules/core/hooks/usePersistedValue.tsx';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const DEFAULT_VALUE = 23; const DEFAULT_VALUE = 23;
const MIN_VALUE = 1; const MIN_VALUE = 1;
@@ -45,8 +46,8 @@ export const WebUIUpdateIntervalSetting = ({
persistUpdateCheckInterval( persistUpdateCheckInterval(
webUIUpdateCheckInterval === 0 ? currentUpdateCheckInterval : webUIUpdateCheckInterval, webUIUpdateCheckInterval === 0 ? currentUpdateCheckInterval : webUIUpdateCheckInterval,
); );
mutateSettings({ variables: { input: { settings: { webUIUpdateCheckInterval } } } }).catch(() => mutateSettings({ variables: { input: { settings: { webUIUpdateCheckInterval } } } }).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}, },
[currentUpdateCheckInterval], [currentUpdateCheckInterval],

View File

@@ -34,6 +34,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts'; import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const Appearance = () => { export const Appearance = () => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
@@ -54,8 +55,8 @@ export const Appearance = () => {
settings, settings,
request: { loading, error, refetch }, request: { loading, error, refetch },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataThemeSettings>(() => const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataThemeSettings>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
const isDarkMode = const isDarkMode =

View File

@@ -31,6 +31,7 @@ import {
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts'; import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts';
import { ServerSettings as GqlServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings as GqlServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type ServerSettingsType = Pick< type ServerSettingsType = Pick<
GqlServerSettings, GqlServerSettings,
@@ -112,7 +113,7 @@ export const ServerSettings = () => {
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const updateMetadataServerSettings = createUpdateMetadataServerSettings< const updateMetadataServerSettings = createUpdateMetadataServerSettings<
keyof Pick<MetadataUpdateSettings, 'serverInformAvailableUpdate'> keyof Pick<MetadataUpdateSettings, 'serverInformAvailableUpdate'>
>(() => makeToast(t('global.error.label.failed_to_save_changes'), 'error')); >((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
const { const {
data, data,
@@ -136,8 +137,8 @@ export const ServerSettings = () => {
setting: Setting, setting: Setting,
value: ServerSettingsType[Setting], value: ServerSettingsType[Setting],
) => { ) => {
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}; };

View File

@@ -30,6 +30,7 @@ import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.t
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export function Settings() { export function Settings() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -52,7 +53,7 @@ export function Settings() {
await triggerClearServerCache(); await triggerClearServerCache();
makeToast(t('settings.clear_cache.label.success'), 'success'); makeToast(t('settings.clear_cache.label.success'), 'success');
} catch (e) { } catch (e) {
makeToast(t('settings.clear_cache.label.failure'), 'error'); makeToast(t('settings.clear_cache.label.failure'), 'error', getErrorMessage(e));
} }
}; };

View File

@@ -32,6 +32,7 @@ import {
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts'; import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts';
import { ServerSettings } from '@/modules/settings/Settings.types.ts'; import { ServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
type WebUISettingsType = Pick< type WebUISettingsType = Pick<
ServerSettings, ServerSettings,
@@ -138,7 +139,7 @@ export const WebUISettings = () => {
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const updateMetadataServerSettings = createUpdateMetadataServerSettings< const updateMetadataServerSettings = createUpdateMetadataServerSettings<
keyof Pick<MetadataUpdateSettings, 'webUIInformAvailableUpdate'> keyof Pick<MetadataUpdateSettings, 'webUIInformAvailableUpdate'>
>(() => makeToast(t('global.error.label.failed_to_save_changes'), 'error')); >((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
const { const {
data, data,
@@ -158,8 +159,8 @@ export const WebUISettings = () => {
requestManager.graphQLClient.client.cache.evict({ fieldName: 'checkForWebUIUpdate' }); requestManager.graphQLClient.client.cache.evict({ fieldName: 'checkForWebUIUpdate' });
} }
mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch(() => mutateSettings({ variables: { input: { settings: { [setting]: value } } } }).catch((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
}; };

View File

@@ -27,6 +27,7 @@ import { GetCategoriesSettingsQueryVariables, GetSourceSettingsQuery } from '@/l
import { GET_SOURCE_SETTINGS } from '@/lib/graphql/queries/SourceQuery.ts'; import { GET_SOURCE_SETTINGS } from '@/lib/graphql/queries/SourceQuery.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { PreferenceProps } from '@/modules/source/Source.types.ts'; import { PreferenceProps } from '@/modules/source/Source.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
function getPrefComponent(type: string) { function getPrefComponent(type: string) {
switch (type) { switch (type) {
@@ -73,7 +74,9 @@ export function SourceConfigure() {
(type, value) => { (type, value) => {
requestManager requestManager
.setSourcePreferences(sourceId, { position, [type]: value }) .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) { if (loading) {

View File

@@ -52,6 +52,7 @@ import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { GridLayout } from '@/modules/core/Core.types.ts'; import { GridLayout } from '@/modules/core/Core.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const DEFAULT_SOURCE: Pick<SourceType, 'id'> = { id: '-1' }; const DEFAULT_SOURCE: Pick<SourceType, 'id'> = { id: '-1' };
@@ -290,8 +291,8 @@ export function SourceMangas() {
const filters = source?.filters ?? []; const filters = source?.filters ?? [];
const { savedSearches = {} } = useGetSourceMetadata(source ?? DEFAULT_SOURCE); const { savedSearches = {} } = useGetSourceMetadata(source ?? DEFAULT_SOURCE);
const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(source ?? { id: '-1' }, () => const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(source ?? { id: '-1' }, (e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
const selectSavedSearch = useCallback( const selectSavedSearch = useCallback(

View File

@@ -19,7 +19,7 @@ import DialogActions from '@mui/material/DialogActions';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import { jsonrepair } from 'jsonrepair'; 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 { AppTheme, isThemeNameUnique } from '@/modules/theme/services/AppThemes.ts';
import { import {
createUpdateMetadataServerSettings, createUpdateMetadataServerSettings,
@@ -203,10 +203,11 @@ export const ThemeCreationDialog = ({
); );
bindDialogProps.onClose(e); bindDialogProps.onClose(e);
}) })
.catch(() => .catch((updateError) =>
makeToast( makeToast(
t(dialogModeToTranslationKey[mode].failure, { theme: theme.getName() }), t(dialogModeToTranslationKey[mode].failure, { theme: theme.getName() }),
'error', 'error',
getErrorMessage(updateError),
), ),
) )
.finally(() => setIsCreating(false)); .finally(() => setIsCreating(false));

View File

@@ -21,6 +21,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'
import { CreateThemeButton } from '@/modules/theme/components/CreateThemeButton.tsx'; import { CreateThemeButton } from '@/modules/theme/components/CreateThemeButton.tsx';
import { ThemePreview } from '@/modules/theme/components/ThemePreview.tsx'; import { ThemePreview } from '@/modules/theme/components/ThemePreview.tsx';
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts'; import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const ThemeList = () => { export const ThemeList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -80,10 +81,11 @@ export const ThemeList = () => {
'success', 'success',
), ),
) )
.catch(() => .catch((e) =>
makeToast( makeToast(
t('settings.appearance.theme.delete.failure', { theme: theme.getName() }), t('settings.appearance.theme.delete.failure', { theme: theme.getName() }),
'error', 'error',
getErrorMessage(e),
), ),
); );
}} }}

View File

@@ -25,6 +25,7 @@ import { createTheme } from '@/modules/theme/services/ThemeCreator.ts';
import { ThemeCreationDialog } from '@/modules/theme/components/CreateThemeDialog.tsx'; import { ThemeCreationDialog } from '@/modules/theme/components/CreateThemeDialog.tsx';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx'; import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const ThemePreviewBadge = styled(Box)(() => ({ const ThemePreviewBadge = styled(Box)(() => ({
width: '15px', width: '15px',
@@ -93,7 +94,13 @@ export const ThemePreview = ({ theme, onDelete }: { theme: AppTheme; onDelete: (
makeToast(t('settings.appearance.theme.select.fonts.loading'), 'info'); makeToast(t('settings.appearance.theme.select.fonts.loading'), 'info');
loadThemeFonts(theme.muiTheme) loadThemeFonts(theme.muiTheme)
.then(() => setAppTheme(theme.id)) .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),
),
);
}} }}
> >
<Stack sx={{ height: '100%', m: 0 }}> <Stack sx={{ height: '100%', m: 0 }}>

View File

@@ -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 { GET_MANGA_TRACK_RECORDS } from '@/lib/graphql/queries/MangaQuery.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => { const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => {
if (id === searchModeForTracker) { if (id === searchModeForTracker) {
@@ -78,7 +79,7 @@ export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'ti
fetchedLatestTrackDataRef.current = true; fetchedLatestTrackDataRef.current = true;
Promise.all( Promise.all(
mangaTrackRecords.map((trackRecord) => requestManager.fetchTrackBind(trackRecord.id).response), mangaTrackRecords.map((trackRecord) => 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]); }, [mangaTrackRecords]);
const trackerComponents = useMemo( const trackerComponents = useMemo(

View File

@@ -34,6 +34,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'
import { MangaType } from '@/lib/graphql/generated/graphql.ts'; import { MangaType } from '@/lib/graphql/generated/graphql.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const TrackerSearch = ({ export const TrackerSearch = ({
manga, manga,
@@ -90,7 +91,7 @@ export const TrackerSearch = ({
makeToast(t('manga.action.track.add.label.success'), 'success'); makeToast(t('manga.action.track.add.label.success'), 'success');
closeSearchMode(); 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 ( return (

View File

@@ -25,6 +25,7 @@ import { PasswordTextField } from '@/modules/core/components/inputs/PasswordText
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { Trackers, TTrackerSearch } from '@/modules/tracker/services/Trackers.ts'; import { Trackers, TTrackerSearch } from '@/modules/tracker/services/Trackers.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => { export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -42,7 +43,7 @@ export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) =>
try { try {
await logoutFromTracker({ variables: { trackerId: tracker.id } }); await logoutFromTracker({ variables: { trackerId: tracker.id } });
} catch (e) { } 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 { try {
await loginTrackerCredentials({ variables: { input: { trackerId: tracker.id, username, password } } }); await loginTrackerCredentials({ variables: { input: { trackerId: tracker.id, username, password } } });
} catch (e) { } 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));
} }
}; };

View File

@@ -40,6 +40,7 @@ import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines
import { SelectSetting, SelectSettingValue } from '@/modules/core/components/settings/SelectSetting.tsx'; import { SelectSetting, SelectSettingValue } from '@/modules/core/components/settings/SelectSetting.tsx';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { TrackRecordType } from '@/lib/graphql/generated/graphql.ts'; import { TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const TrackerActiveLink = ({ children, url }: { children: React.ReactNode; url: string }) => ( const TrackerActiveLink = ({ children, url }: { children: React.ReactNode; url: string }) => (
<Link href={url} rel="noreferrer" target="_blank" underline="none" color="inherit"> <Link href={url} rel="noreferrer" target="_blank" underline="none" color="inherit">
@@ -69,7 +70,7 @@ const TrackerActiveRemoveBind = ({
requestManager requestManager
.unbindTracker(trackerRecordId, removeRemoteTracking) .unbindTracker(trackerRecordId, removeRemoteTracking)
.response.then(() => makeToast(t('manga.action.track.remove.label.success'), 'success')) .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 ( return (
@@ -250,7 +251,9 @@ export const TrackerActiveCard = ({
const updateTrackerBind = (patch: Parameters<typeof requestManager.updateTrackerBind>[1]) => { const updateTrackerBind = (patch: Parameters<typeof requestManager.updateTrackerBind>[1]) => {
requestManager requestManager
.updateTrackerBind(trackRecord.id, patch) .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 ( return (

View File

@@ -12,6 +12,7 @@ import { useNavigate } from 'react-router-dom';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const TrackerOAuthLogin = () => { export const TrackerOAuthLogin = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -36,7 +37,7 @@ export const TrackerOAuthLogin = () => {
}, },
}); });
} catch (e) { } 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 }); navigate(AppRoutes.tracker.path, { replace: true });

View File

@@ -28,6 +28,7 @@ import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'
import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts'; import { GET_TRACKERS_SETTINGS } from '@/lib/graphql/queries/TrackerQuery.ts';
import { GetTrackersSettingsQuery } from '@/lib/graphql/generated/graphql.ts'; import { GetTrackersSettingsQuery } from '@/lib/graphql/generated/graphql.ts';
import { MetadataTrackingSettings } from '@/modules/tracker/Tracker.types.ts'; import { MetadataTrackingSettings } from '@/modules/tracker/Tracker.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
export const TrackingSettings = () => { export const TrackingSettings = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -42,8 +43,8 @@ export const TrackingSettings = () => {
loading: areMetadataServerSettingsLoading, loading: areMetadataServerSettingsLoading,
request: { error: metadataServerSettingsError, refetch: refetchServerMetadataSettings }, request: { error: metadataServerSettingsError, refetch: refetchServerMetadataSettings },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const updateTrackingSettings = createUpdateMetadataServerSettings<keyof MetadataTrackingSettings>(() => const updateTrackingSettings = createUpdateMetadataServerSettings<keyof MetadataTrackingSettings>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error'), makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
); );
const { const {

View File

@@ -38,6 +38,7 @@ import { ChapterIdInfo, ChapterMangaInfo } from '@/modules/chapter/services/Chap
import { makeToast } from '@/modules/core/utils/Toast.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts';
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx'; import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
const groupByDate = (updates: Pick<ChapterType, 'fetchedAt'>[]): [date: string, items: number][] => { const groupByDate = (updates: Pick<ChapterType, 'fetchedAt'>[]): [date: string, items: number][] => {
if (!updates.length) { if (!updates.length) {
@@ -115,14 +116,16 @@ export const Updates: React.FC = () => {
try { try {
await requestManager.addChapterToDownloadQueue(chapter.id).response; await requestManager.addChapterToDownloadQueue(chapter.id).response;
} catch (e) { } 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) => { const downloadChapter = (chapter: ChapterIdInfo) => {
requestManager requestManager
.addChapterToDownloadQueue(chapter.id) .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(() => { const loadMore = useCallback(() => {