mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 11:55:03 +03:00
Updated AdminX error handling to display validation errors correctly (#19210)
fixes ADM-44
This commit is contained in:
parent
2809703c76
commit
cc4176f0bf
@ -3,7 +3,7 @@ import {showToast} from '@tryghost/admin-x-design-system';
|
|||||||
import {useCallback} from 'react';
|
import {useCallback} from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import {useFramework} from '../providers/FrameworkProvider';
|
import {useFramework} from '../providers/FrameworkProvider';
|
||||||
import {APIError, JSONError, ValidationError} from '../utils/errors';
|
import {APIError, ValidationError} from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic error handling for API calls. This is enabled by default for queries (can be disabled by
|
* Generic error handling for API calls. This is enabled by default for queries (can be disabled by
|
||||||
@ -20,8 +20,7 @@ const useHandleError = () => {
|
|||||||
* so this toast is intended as a worst-case fallback message when we don't know what else to do.
|
* so this toast is intended as a worst-case fallback message when we don't know what else to do.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
type HandleErrorReturnType = void | any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
const handleError = useCallback((error: unknown, {withToast = true}: {withToast?: boolean} = {}) => {
|
||||||
const handleError = useCallback((error: unknown, {withToast = true}: {withToast?: boolean} = {}) : HandleErrorReturnType => {
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
@ -41,10 +40,6 @@ const useHandleError = () => {
|
|||||||
|
|
||||||
toast.remove();
|
toast.remove();
|
||||||
|
|
||||||
if (error instanceof JSONError && error.response?.status === 422) {
|
|
||||||
return error.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof APIError && error.response?.status === 418) {
|
if (error instanceof APIError && error.response?.status === 418) {
|
||||||
// We use this status in tests to indicate the API request was not mocked -
|
// We use this status in tests to indicate the API request was not mocked -
|
||||||
// don't show a toast because it may block clicking things in the test
|
// don't show a toast because it may block clicking things in the test
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import AdvancedThemeSettings from './theme/AdvancedThemeSettings';
|
import AdvancedThemeSettings from './theme/AdvancedThemeSettings';
|
||||||
import InvalidThemeModal from './theme/InvalidThemeModal';
|
import InvalidThemeModal, {FatalErrors} from './theme/InvalidThemeModal';
|
||||||
import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react';
|
import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react';
|
||||||
import OfficialThemes from './theme/OfficialThemes';
|
import OfficialThemes from './theme/OfficialThemes';
|
||||||
import React, {useEffect, useRef, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import ThemeInstalledModal from './theme/ThemeInstalledModal';
|
import ThemeInstalledModal from './theme/ThemeInstalledModal';
|
||||||
import ThemePreview from './theme/ThemePreview';
|
import ThemePreview from './theme/ThemePreview';
|
||||||
import {Breadcrumbs, Button, ConfirmationModal, FileUpload, LimitModal, Modal, PageHeader, TabView, showToast} from '@tryghost/admin-x-design-system';
|
import {Breadcrumbs, Button, ConfirmationModal, FileUpload, LimitModal, Modal, PageHeader, TabView, showToast} from '@tryghost/admin-x-design-system';
|
||||||
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
||||||
import {InstalledTheme, Theme, ThemesInstallResponseType, isDefaultOrLegacyTheme, useActivateTheme, useBrowseThemes, useInstallTheme, useUploadTheme} from '@tryghost/admin-x-framework/api/themes';
|
import {InstalledTheme, Theme, ThemesInstallResponseType, isDefaultOrLegacyTheme, useActivateTheme, useBrowseThemes, useInstallTheme, useUploadTheme} from '@tryghost/admin-x-framework/api/themes';
|
||||||
|
import {JSONError} from '@tryghost/admin-x-framework/errors';
|
||||||
import {OfficialTheme} from '../../providers/SettingsAppProvider';
|
import {OfficialTheme} from '../../providers/SettingsAppProvider';
|
||||||
import {useHandleError} from '@tryghost/admin-x-framework/hooks';
|
import {useHandleError} from '@tryghost/admin-x-framework/hooks';
|
||||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||||
@ -61,14 +62,6 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
|||||||
|
|
||||||
const [isUploading, setUploading] = useState(false);
|
const [isUploading, setUploading] = useState(false);
|
||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const handleRetry = () => {
|
|
||||||
if (fileInputRef?.current) {
|
|
||||||
fileInputRef.current.click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (limiter) {
|
if (limiter) {
|
||||||
// Sending a bad string to make sure it fails (empty string isn't valid)
|
// Sending a bad string to make sure it fails (empty string isn't valid)
|
||||||
@ -132,7 +125,7 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
|||||||
onActivate?: () => void
|
onActivate?: () => void
|
||||||
}) => {
|
}) => {
|
||||||
let data: ThemesInstallResponseType | undefined;
|
let data: ThemesInstallResponseType | undefined;
|
||||||
let fatalErrors = null;
|
let fatalErrors: FatalErrors | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
@ -140,9 +133,11 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
|||||||
setUploading(false);
|
setUploading(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
const errorsJson = await handleError(e) as {errors?: []};
|
|
||||||
if (errorsJson?.errors) {
|
if (e instanceof JSONError && e.response?.status === 422 && e.data?.errors) {
|
||||||
fatalErrors = errorsJson.errors;
|
fatalErrors = (e.data.errors as any) as FatalErrors;
|
||||||
|
} else {
|
||||||
|
handleError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +150,7 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
|||||||
fatalErrors,
|
fatalErrors,
|
||||||
onRetry: async (modal) => {
|
onRetry: async (modal) => {
|
||||||
modal?.remove();
|
modal?.remove();
|
||||||
handleRetry();
|
handleUpload();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ type FatalError = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type FatalErrors = FatalError[];
|
export type FatalErrors = FatalError[];
|
||||||
|
|
||||||
export const ThemeProblemView = ({problem}:{problem: ThemeProblem}) => {
|
export const ThemeProblemView = ({problem}:{problem: ThemeProblem}) => {
|
||||||
const [isExpanded, setExpanded] = useState(false);
|
const [isExpanded, setExpanded] = useState(false);
|
||||||
@ -63,8 +63,7 @@ const InvalidThemeModal: React.FC<{
|
|||||||
if (fatalErrors) {
|
if (fatalErrors) {
|
||||||
warningPrompt = <div className="mt-10">
|
warningPrompt = <div className="mt-10">
|
||||||
<List title="Errors">
|
<List title="Errors">
|
||||||
{fatalErrors?.map((error: any) => error?.details?.errors?.map((err: any) => <ThemeProblemView problem={err} />
|
{fatalErrors?.map(error => error?.details?.errors?.map(err => <ThemeProblemView problem={err} />))}
|
||||||
))}
|
|
||||||
</List>
|
</List>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user