mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Ensured JSON decodedoes not happen for endpoints returning empty body
This commit is contained in:
parent
bd8ea17c84
commit
dd9ee044eb
6 changed files with 53 additions and 51 deletions
|
@ -79,7 +79,7 @@ export class ShlinkApiClient {
|
||||||
this.performRequest<ShortUrl>(`/short-urls/${shortCode}`, 'GET', { domain });
|
this.performRequest<ShortUrl>(`/short-urls/${shortCode}`, 'GET', { domain });
|
||||||
|
|
||||||
public readonly deleteShortUrl = async (shortCode: string, domain?: OptionalString): Promise<void> =>
|
public readonly deleteShortUrl = async (shortCode: string, domain?: OptionalString): Promise<void> =>
|
||||||
this.performRequest(`/short-urls/${shortCode}`, 'DELETE', { domain })
|
this.performRequest<void>(`/short-urls/${shortCode}`, 'DELETE', { domain })
|
||||||
.then(() => {});
|
.then(() => {});
|
||||||
|
|
||||||
public readonly updateShortUrl = async (
|
public readonly updateShortUrl = async (
|
||||||
|
@ -95,11 +95,11 @@ export class ShlinkApiClient {
|
||||||
.then(({ data, stats }) => ({ tags: data, stats }));
|
.then(({ data, stats }) => ({ tags: data, stats }));
|
||||||
|
|
||||||
public readonly deleteTags = async (tags: string[]): Promise<{ tags: string[] }> =>
|
public readonly deleteTags = async (tags: string[]): Promise<{ tags: string[] }> =>
|
||||||
this.performRequest('/tags', 'DELETE', { tags })
|
this.performRequest<void>('/tags', 'DELETE', { tags })
|
||||||
.then(() => ({ tags }));
|
.then(() => ({ tags }));
|
||||||
|
|
||||||
public readonly editTag = async (oldName: string, newName: string): Promise<{ oldName: string; newName: string }> =>
|
public readonly editTag = async (oldName: string, newName: string): Promise<{ oldName: string; newName: string }> =>
|
||||||
this.performRequest('/tags', 'PUT', {}, { oldName, newName })
|
this.performRequest<void>('/tags', 'PUT', {}, { oldName, newName })
|
||||||
.then(() => ({ oldName, newName }));
|
.then(() => ({ oldName, newName }));
|
||||||
|
|
||||||
public readonly health = async (): Promise<ShlinkHealth> => this.performRequest<ShlinkHealth>('/health', 'GET');
|
public readonly health = async (): Promise<ShlinkHealth> => this.performRequest<ShlinkHealth>('/health', 'GET');
|
||||||
|
|
|
@ -5,13 +5,15 @@ export class HttpClient {
|
||||||
|
|
||||||
public fetchJson<T>(url: string, options?: RequestInit): Promise<T> {
|
public fetchJson<T>(url: string, options?: RequestInit): Promise<T> {
|
||||||
return this.fetch(url, options).then(async (resp) => {
|
return this.fetch(url, options).then(async (resp) => {
|
||||||
const parsed = await resp.json();
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
throw parsed;
|
throw await resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed as T;
|
try {
|
||||||
|
return (await resp.json()) as T;
|
||||||
|
} catch (e) {
|
||||||
|
return undefined as T;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,10 @@ export const EditTagModal = ({ getColorForKey }: ColorGenerator) => (
|
||||||
const [showColorPicker, toggleColorPicker, , hideColorPicker] = useToggle();
|
const [showColorPicker, toggleColorPicker, , hideColorPicker] = useToggle();
|
||||||
const { editing, error, edited, errorData } = tagEdit;
|
const { editing, error, edited, errorData } = tagEdit;
|
||||||
const saveTag = handleEventPreventingDefault(
|
const saveTag = handleEventPreventingDefault(
|
||||||
async () => editTag({ oldName: tag, newName: newTagName, color })
|
async () => {
|
||||||
.then(toggle)
|
await editTag({ oldName: tag, newName: newTagName, color });
|
||||||
.catch(() => {}),
|
toggle();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
const onClosed = pipe(hideColorPicker, () => edited && tagEdited({ oldName: tag, newName: newTagName, color }));
|
const onClosed = pipe(hideColorPicker, () => edited && tagEdited({ oldName: tag, newName: newTagName, color }));
|
||||||
|
|
||||||
|
|
|
@ -33,35 +33,34 @@ const initialState: TagEdition = {
|
||||||
|
|
||||||
export const tagEdited = createAction<EditTag>(`${REDUCER_PREFIX}/tagEdited`);
|
export const tagEdited = createAction<EditTag>(`${REDUCER_PREFIX}/tagEdited`);
|
||||||
|
|
||||||
export const tagEditReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder, colorGenerator: ColorGenerator) => {
|
export const editTag = (
|
||||||
const editTag = createAsyncThunk(
|
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||||
`${REDUCER_PREFIX}/editTag`,
|
colorGenerator: ColorGenerator,
|
||||||
async ({ oldName, newName, color }: EditTag, { getState }): Promise<EditTag> => {
|
) => createAsyncThunk(
|
||||||
await buildShlinkApiClient(getState).editTag(oldName, newName);
|
`${REDUCER_PREFIX}/editTag`,
|
||||||
colorGenerator.setColorForKey(newName, color);
|
async ({ oldName, newName, color }: EditTag, { getState }): Promise<EditTag> => {
|
||||||
|
await buildShlinkApiClient(getState).editTag(oldName, newName);
|
||||||
|
colorGenerator.setColorForKey(newName, color);
|
||||||
|
|
||||||
return { oldName, newName, color };
|
return { oldName, newName, color };
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { reducer } = createSlice({
|
export const tagEditReducerCreator = (editTagThunk: ReturnType<typeof editTag>) => createSlice({
|
||||||
name: REDUCER_PREFIX,
|
name: REDUCER_PREFIX,
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {},
|
reducers: {},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(editTag.pending, () => ({ editing: true, edited: false, error: false }));
|
builder.addCase(editTagThunk.pending, () => ({ editing: true, edited: false, error: false }));
|
||||||
builder.addCase(
|
builder.addCase(
|
||||||
editTag.rejected,
|
editTagThunk.rejected,
|
||||||
(_, { error }) => ({ editing: false, edited: false, error: true, errorData: parseApiError(error) }),
|
(_, { error }) => ({ editing: false, edited: false, error: true, errorData: parseApiError(error) }),
|
||||||
);
|
);
|
||||||
builder.addCase(editTag.fulfilled, (_, { payload }) => ({
|
builder.addCase(editTagThunk.fulfilled, (_, { payload }) => ({
|
||||||
...pick(['oldName', 'newName'], payload),
|
...pick(['oldName', 'newName'], payload),
|
||||||
editing: false,
|
editing: false,
|
||||||
edited: true,
|
edited: true,
|
||||||
error: false,
|
error: false,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return { reducer, editTag };
|
|
||||||
};
|
|
||||||
|
|
|
@ -111,15 +111,15 @@ export const tagsListReducerCreator = (
|
||||||
{ ...initialState, stats: payload.stats, tags: payload.tags, filteredTags: payload.tags }
|
{ ...initialState, stats: payload.stats, tags: payload.tags, filteredTags: payload.tags }
|
||||||
));
|
));
|
||||||
|
|
||||||
builder.addCase(tagDeleted, (state, { payload: tag }) => ({
|
builder.addCase(tagDeleted, ({ tags, filteredTags, ...rest }, { payload: tag }) => ({
|
||||||
...state,
|
...rest,
|
||||||
tags: rejectTag(state.tags, tag),
|
tags: rejectTag(tags, tag),
|
||||||
filteredTags: rejectTag(state.filteredTags, tag),
|
filteredTags: rejectTag(filteredTags, tag),
|
||||||
}));
|
}));
|
||||||
builder.addCase(tagEdited, (state, { payload }) => ({
|
builder.addCase(tagEdited, ({ tags, filteredTags, ...rest }, { payload }) => ({
|
||||||
...state,
|
...rest,
|
||||||
tags: state.tags.map(renameTag(payload.oldName, payload.newName)).sort(),
|
tags: tags.map(renameTag(payload.oldName, payload.newName)).sort(),
|
||||||
filteredTags: state.filteredTags.map(renameTag(payload.oldName, payload.newName)).sort(),
|
filteredTags: filteredTags.map(renameTag(payload.oldName, payload.newName)).sort(),
|
||||||
}));
|
}));
|
||||||
builder.addCase(createNewVisits, (state, { payload }) => ({
|
builder.addCase(createNewVisits, (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { EditTagModal } from '../helpers/EditTagModal';
|
||||||
import { TagsList } from '../TagsList';
|
import { TagsList } from '../TagsList';
|
||||||
import { filterTags, listTags, tagsListReducerCreator } from '../reducers/tagsList';
|
import { filterTags, listTags, tagsListReducerCreator } from '../reducers/tagsList';
|
||||||
import { tagDeleted, tagDeleteReducerCreator } from '../reducers/tagDelete';
|
import { tagDeleted, tagDeleteReducerCreator } from '../reducers/tagDelete';
|
||||||
import { tagEdited, tagEditReducerCreator } from '../reducers/tagEdit';
|
import { editTag, tagEdited, tagEditReducerCreator } from '../reducers/tagEdit';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
import { TagsCards } from '../TagsCards';
|
import { TagsCards } from '../TagsCards';
|
||||||
import { TagsTable } from '../TagsTable';
|
import { TagsTable } from '../TagsTable';
|
||||||
|
@ -38,7 +38,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
));
|
));
|
||||||
|
|
||||||
// Reducers
|
// Reducers
|
||||||
bottle.serviceFactory('tagEditReducerCreator', tagEditReducerCreator, 'buildShlinkApiClient', 'ColorGenerator');
|
bottle.serviceFactory('tagEditReducerCreator', tagEditReducerCreator, 'editTag');
|
||||||
bottle.serviceFactory('tagEditReducer', prop('reducer'), 'tagEditReducerCreator');
|
bottle.serviceFactory('tagEditReducer', prop('reducer'), 'tagEditReducerCreator');
|
||||||
|
|
||||||
bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'buildShlinkApiClient');
|
bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'buildShlinkApiClient');
|
||||||
|
@ -58,7 +58,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
bottle.serviceFactory('deleteTag', prop('deleteTag'), 'tagDeleteReducerCreator');
|
bottle.serviceFactory('deleteTag', prop('deleteTag'), 'tagDeleteReducerCreator');
|
||||||
bottle.serviceFactory('tagDeleted', () => tagDeleted);
|
bottle.serviceFactory('tagDeleted', () => tagDeleted);
|
||||||
|
|
||||||
bottle.serviceFactory('editTag', prop('editTag'), 'tagEditReducerCreator');
|
bottle.serviceFactory('editTag', editTag, 'buildShlinkApiClient', 'ColorGenerator');
|
||||||
bottle.serviceFactory('tagEdited', () => tagEdited);
|
bottle.serviceFactory('tagEdited', () => tagEdited);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue