diff --git a/src/api/ShlinkApiError.tsx b/src/api/ShlinkApiError.tsx new file mode 100644 index 00000000..8c9e51f5 --- /dev/null +++ b/src/api/ShlinkApiError.tsx @@ -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) && +

Invalid elements: [{errorData.invalidElements.join(', ')}]

+ } + +); diff --git a/src/short-urls/helpers/CreateShortUrlResult.tsx b/src/short-urls/helpers/CreateShortUrlResult.tsx index ad4fa3bc..f5a75ded 100644 --- a/src/short-urls/helpers/CreateShortUrlResult.tsx +++ b/src/short-urls/helpers/CreateShortUrlResult.tsx @@ -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 ( {canBeClosed && } - {errorData?.detail ?? 'An error occurred while creating the URL :('} - {isInvalidArgumentError(errorData) &&

Invalid elements: [{errorData.invalidElements.join(', ')}]

} +
); } diff --git a/src/short-urls/helpers/DeleteShortUrlModal.tsx b/src/short-urls/helpers/DeleteShortUrlModal.tsx index 9fd714f9..119892d2 100644 --- a/src/short-urls/helpers/DeleteShortUrlModal.tsx +++ b/src/short-urls/helpers/DeleteShortUrlModal.tsx @@ -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) && ( - - {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.'} - - )} - {error && !isInvalidDeletionError(errorData) && ( - - {errorData?.detail ?? 'Something went wrong while deleting the URL :('} + {error && ( + + )} diff --git a/src/short-urls/helpers/EditMetaModal.tsx b/src/short-urls/helpers/EditMetaModal.tsx index ca22c2a1..688cf9be 100644 --- a/src/short-urls/helpers/EditMetaModal.tsx +++ b/src/short-urls/helpers/EditMetaModal.tsx @@ -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) => setMaxVisits(Number(e.target.value))} /> + {error && ( - Something went wrong while saving the metadata :( + )} diff --git a/src/short-urls/reducers/shortUrlMeta.ts b/src/short-urls/reducers/shortUrlMeta.ts index 8b4e8a51..c867bf0d 100644 --- a/src/short-urls/reducers/shortUrlMeta.ts +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -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, ShortUrlIdentifier { meta: ShortUrlMeta; } +export interface ShortUrlMetaEditionFailedAction extends Action { + errorData?: ProblemDetailsError; +} + const initialState: ShortUrlMetaEdition = { shortCode: null, meta: {}, @@ -30,9 +36,9 @@ const initialState: ShortUrlMetaEdition = { error: false, }; -export default buildReducer({ +export default buildReducer({ [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({ shortCode, meta, domain, type: SHORT_URL_META_EDITED }); } catch (e) { - dispatch({ type: EDIT_SHORT_URL_META_ERROR }); + dispatch({ type: EDIT_SHORT_URL_META_ERROR, errorData: e.response?.data }); throw e; } diff --git a/src/utils/services/ShlinkApiClient.ts b/src/utils/services/ShlinkApiClient.ts index 706a72eb..b04b7738 100644 --- a/src/utils/services/ShlinkApiClient.ts +++ b/src/utils/services/ShlinkApiClient.ts @@ -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); diff --git a/src/utils/services/ShlinkApiClientBuilder.ts b/src/utils/services/ShlinkApiClientBuilder.ts index d2ba24cd..328a1c8a 100644 --- a/src/utils/services/ShlinkApiClientBuilder.ts +++ b/src/utils/services/ShlinkApiClientBuilder.ts @@ -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 = {}; const isGetState = (getStateOrSelectedServer: GetState | ServerWithId): getStateOrSelectedServer is GetState => diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index b515ff06..9f5f9225 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -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; diff --git a/test/short-urls/helpers/DeleteShortUrlModal.test.tsx b/test/short-urls/helpers/DeleteShortUrlModal.test.tsx index 06ce4864..d39fdb27 100644 --- a/test/short-urls/helpers/DeleteShortUrlModal.test.tsx +++ b/test/short-urls/helpers/DeleteShortUrlModal.test.tsx @@ -33,28 +33,6 @@ describe('', () => { 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, expectedMessage) => { - const wrapper = createWrapper({ - loading: false, - error: true, - shortCode: 'abc123', - errorData: Mock.of(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,