mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 01:20:24 +03:00
Created component holding the logic to render Shlink API errors
This commit is contained in:
parent
f69f791790
commit
51379eb2a0
9 changed files with 43 additions and 39 deletions
15
src/api/ShlinkApiError.tsx
Normal file
15
src/api/ShlinkApiError.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { isInvalidArgumentError, ProblemDetailsError } from '../utils/services/types';
|
||||
|
||||
interface ShlinkApiErrorProps {
|
||||
errorData?: ProblemDetailsError;
|
||||
fallbackMessage?: string;
|
||||
}
|
||||
|
||||
export const ShlinkApiError = ({ errorData, fallbackMessage }: ShlinkApiErrorProps) => (
|
||||
<>
|
||||
{errorData?.detail ?? fallbackMessage}
|
||||
{isInvalidArgumentError(errorData) &&
|
||||
<p className="mb-0">Invalid elements: [{errorData.invalidElements.join(', ')}]</p>
|
||||
}
|
||||
</>
|
||||
);
|
|
@ -9,7 +9,7 @@ import { ShortUrlCreation } from '../reducers/shortUrlCreation';
|
|||
import { StateFlagTimeout } from '../../utils/helpers/hooks';
|
||||
import { Result } from '../../utils/Result';
|
||||
import './CreateShortUrlResult.scss';
|
||||
import { isInvalidArgumentError } from '../../utils/services/types';
|
||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
||||
|
||||
export interface CreateShortUrlResultProps extends ShortUrlCreation {
|
||||
resetCreateShortUrl: () => void;
|
||||
|
@ -29,8 +29,7 @@ const CreateShortUrlResult = (useStateFlagTimeout: StateFlagTimeout) => (
|
|||
return (
|
||||
<Result type="error" className="mt-3">
|
||||
{canBeClosed && <FontAwesomeIcon icon={closeIcon} className="float-right pointer" onClick={resetCreateShortUrl} />}
|
||||
{errorData?.detail ?? 'An error occurred while creating the URL :('}
|
||||
{isInvalidArgumentError(errorData) && <p>Invalid elements: [{errorData.invalidElements.join(', ')}]</p>}
|
||||
<ShlinkApiError errorData={errorData} fallbackMessage="An error occurred while creating the URL :(" />
|
||||
</Result>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ShortUrlModalProps } from '../data';
|
|||
import { handleEventPreventingDefault, OptionalString } from '../../utils/utils';
|
||||
import { Result } from '../../utils/Result';
|
||||
import { isInvalidDeletionError } from '../../utils/services/types';
|
||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
||||
|
||||
interface DeleteShortUrlModalConnectProps extends ShortUrlModalProps {
|
||||
shortUrlDeletion: ShortUrlDeletion;
|
||||
|
@ -49,15 +50,9 @@ const DeleteShortUrlModal = (
|
|||
onChange={(e) => setInputValue(e.target.value)}
|
||||
/>
|
||||
|
||||
{error && isInvalidDeletionError(errorData) && (
|
||||
<Result type="warning" small className="mt-2">
|
||||
{errorData.threshold && `This short URL has received more than ${errorData.threshold} visits, and therefore, it cannot be deleted.`}
|
||||
{!errorData.threshold && 'This short URL has received too many visits, and therefore, it cannot be deleted.'}
|
||||
</Result>
|
||||
)}
|
||||
{error && !isInvalidDeletionError(errorData) && (
|
||||
<Result type="error" small className="mt-2">
|
||||
{errorData?.detail ?? 'Something went wrong while deleting the URL :('}
|
||||
{error && (
|
||||
<Result type={isInvalidDeletionError(errorData) ? 'warning' : 'error'} small className="mt-2">
|
||||
<ShlinkApiError errorData={errorData} fallbackMessage="Something went wrong while deleting the URL :(" />
|
||||
</Result>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
|
|
@ -11,6 +11,7 @@ import { formatIsoDate } from '../../utils/helpers/date';
|
|||
import { ShortUrl, ShortUrlMeta, ShortUrlModalProps } from '../data';
|
||||
import { handleEventPreventingDefault, Nullable, OptionalString } from '../../utils/utils';
|
||||
import { Result } from '../../utils/Result';
|
||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
||||
|
||||
interface EditMetaModalConnectProps extends ShortUrlModalProps {
|
||||
shortUrlMeta: ShortUrlMetaEdition;
|
||||
|
@ -27,7 +28,7 @@ const dateOrNull = (shortUrl: ShortUrl | undefined, dateName: 'validSince' | 'va
|
|||
const EditMetaModal = (
|
||||
{ isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMeta, resetShortUrlMeta }: EditMetaModalConnectProps,
|
||||
) => {
|
||||
const { saving, error } = shortUrlMeta;
|
||||
const { saving, error, errorData } = shortUrlMeta;
|
||||
const url = shortUrl && (shortUrl.shortUrl || '');
|
||||
const [ validSince, setValidSince ] = useState(dateOrNull(shortUrl, 'validSince'));
|
||||
const [ validUntil, setValidUntil ] = useState(dateOrNull(shortUrl, 'validUntil'));
|
||||
|
@ -78,9 +79,13 @@ const EditMetaModal = (
|
|||
onChange={(e: ChangeEvent<HTMLInputElement>) => setMaxVisits(Number(e.target.value))}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{error && (
|
||||
<Result type="error" small className="mt-2">
|
||||
Something went wrong while saving the metadata :(
|
||||
<ShlinkApiError
|
||||
errorData={errorData}
|
||||
fallbackMessage="Something went wrong while saving the metadata :("
|
||||
/>
|
||||
</Result>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { GetState } from '../../container/types';
|
|||
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
|
||||
import { OptionalString } from '../../utils/utils';
|
||||
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
|
||||
import { ProblemDetailsError } from '../../utils/services/types';
|
||||
|
||||
/* eslint-disable padding-line-between-statements */
|
||||
export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START';
|
||||
|
@ -17,12 +18,17 @@ export interface ShortUrlMetaEdition {
|
|||
meta: ShortUrlMeta;
|
||||
saving: boolean;
|
||||
error: boolean;
|
||||
errorData?: ProblemDetailsError;
|
||||
}
|
||||
|
||||
export interface ShortUrlMetaEditedAction extends Action<string>, ShortUrlIdentifier {
|
||||
meta: ShortUrlMeta;
|
||||
}
|
||||
|
||||
export interface ShortUrlMetaEditionFailedAction extends Action<string> {
|
||||
errorData?: ProblemDetailsError;
|
||||
}
|
||||
|
||||
const initialState: ShortUrlMetaEdition = {
|
||||
shortCode: null,
|
||||
meta: {},
|
||||
|
@ -30,9 +36,9 @@ const initialState: ShortUrlMetaEdition = {
|
|||
error: false,
|
||||
};
|
||||
|
||||
export default buildReducer<ShortUrlMetaEdition, ShortUrlMetaEditedAction>({
|
||||
export default buildReducer<ShortUrlMetaEdition, ShortUrlMetaEditedAction & ShortUrlMetaEditionFailedAction>({
|
||||
[EDIT_SHORT_URL_META_START]: (state) => ({ ...state, saving: true, error: false }),
|
||||
[EDIT_SHORT_URL_META_ERROR]: (state) => ({ ...state, saving: false, error: true }),
|
||||
[EDIT_SHORT_URL_META_ERROR]: (state, { errorData }) => ({ ...state, saving: false, error: true, errorData }),
|
||||
[SHORT_URL_META_EDITED]: (_, { shortCode, meta }) => ({ shortCode, meta, saving: false, error: false }),
|
||||
[RESET_EDIT_SHORT_URL_META]: () => initialState,
|
||||
}, initialState);
|
||||
|
@ -49,7 +55,7 @@ export const editShortUrlMeta = (buildShlinkApiClient: ShlinkApiClientBuilder) =
|
|||
await updateShortUrlMeta(shortCode, domain, meta);
|
||||
dispatch<ShortUrlMetaEditedAction>({ shortCode, meta, domain, type: SHORT_URL_META_EDITED });
|
||||
} catch (e) {
|
||||
dispatch({ type: EDIT_SHORT_URL_META_ERROR });
|
||||
dispatch<ShortUrlMetaEditionFailedAction>({ type: EDIT_SHORT_URL_META_ERROR, errorData: e.response?.data });
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
ShlinkVisitsOverview,
|
||||
} from './types';
|
||||
|
||||
// TODO Move this file to api module
|
||||
|
||||
const buildShlinkBaseUrl = (url: string, apiVersion: number) => url ? `${url}/rest/v${apiVersion}` : '';
|
||||
const rejectNilProps = reject(isNil);
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import { hasServerData, SelectedServer, ServerWithId } from '../../servers/data'
|
|||
import { GetState } from '../../container/types';
|
||||
import ShlinkApiClient from './ShlinkApiClient';
|
||||
|
||||
// TODO Move this file to api module
|
||||
|
||||
const apiClients: Record<string, ShlinkApiClient> = {};
|
||||
|
||||
const isGetState = (getStateOrSelectedServer: GetState | ServerWithId): getStateOrSelectedServer is GetState =>
|
||||
|
|
|
@ -2,6 +2,8 @@ import { Visit } from '../../visits/types'; // FIXME Should be defined as part o
|
|||
import { ShortUrl, ShortUrlMeta } from '../../short-urls/data'; // FIXME Should be defined as part of this module
|
||||
import { OptionalString } from '../utils';
|
||||
|
||||
// TODO Move this file to api module
|
||||
|
||||
export interface ShlinkShortUrlsResponse {
|
||||
data: ShortUrl[];
|
||||
pagination: ShlinkPaginator;
|
||||
|
|
|
@ -33,28 +33,6 @@ describe('<DeleteShortUrlModal />', () => {
|
|||
afterEach(() => wrapper?.unmount());
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
||||
it.each([
|
||||
[
|
||||
{ type: 'INVALID_SHORTCODE_DELETION' },
|
||||
'This short URL has received too many visits, and therefore, it cannot be deleted.',
|
||||
],
|
||||
[
|
||||
{ type: 'INVALID_SHORTCODE_DELETION', threshold: 8 },
|
||||
'This short URL has received more than 8 visits, and therefore, it cannot be deleted.',
|
||||
],
|
||||
])('shows threshold error message when threshold error occurs', (errorData: Partial<ProblemDetailsError>, expectedMessage) => {
|
||||
const wrapper = createWrapper({
|
||||
loading: false,
|
||||
error: true,
|
||||
shortCode: 'abc123',
|
||||
errorData: Mock.of<ProblemDetailsError>(errorData),
|
||||
});
|
||||
const warning = wrapper.find(Result).filterWhere((result) => result.prop('type') === 'warning');
|
||||
|
||||
expect(warning).toHaveLength(1);
|
||||
expect(warning.html()).toContain(expectedMessage);
|
||||
});
|
||||
|
||||
it('shows generic error when non-threshold error occurs', () => {
|
||||
const wrapper = createWrapper({
|
||||
loading: false,
|
||||
|
|
Loading…
Reference in a new issue