Implemented delete tag behavior

This commit is contained in:
Alejandro Celaya 2018-08-18 16:39:47 +02:00
parent f480e34f67
commit 1dee478234
6 changed files with 129 additions and 21 deletions

View file

@ -54,7 +54,12 @@ export class ShlinkApiClient {
.then(resp => resp.data.tags.data)
.catch(e => this._handleAuthError(e, this.listTags, []));
_performRequest = async (url, method = 'GET', params = {}, data = {}) => {
deleteTags = tags =>
this._performRequest('/tags', 'DELETE', { tags })
.then(() => ({ tags }))
.catch(e => this._handleAuthError(e, this.deleteTag, []));
_performRequest = async (url, method = 'GET', query = {}, body = {}) => {
if (isEmpty(this._token)) {
this._token = await this._authenticate();
}
@ -63,8 +68,8 @@ export class ShlinkApiClient {
method,
url: `${this._baseUrl}${url}`,
headers: { 'Authorization': `Bearer ${this._token}` },
params,
data,
params: query,
data: body,
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'brackets' })
}).then(resp => {
// Save new token

View file

@ -8,6 +8,7 @@ import shortUrlCreationResultReducer from '../short-urls/reducers/shortUrlCreati
import shortUrlVisitsReducer from '../short-urls/reducers/shortUrlVisits';
import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags';
import tagsListReducer from '../tags/reducers/tagsList';
import tagDeleteReducer from '../tags/reducers/tagDelete';
export default combineReducers({
servers: serversReducer,
@ -18,4 +19,5 @@ export default combineReducers({
shortUrlVisits: shortUrlVisitsReducer,
shortUrlTags: shortUrlTagsReducer,
tagsList: tagsListReducer,
tagDelete: tagDeleteReducer,
});

View file

@ -22,7 +22,11 @@ export class TagsList extends React.Component {
}
if (tagsList.error) {
return <div className="bg-danger p-2 text-white text-center">Error loading tags :(</div>;
return (
<div className="col-12">
<div className="bg-danger p-2 text-white text-center">Error loading tags :(</div>
</div>
);
}
const tagsCount = tagsList.tags.length;

View file

@ -1,28 +1,74 @@
import React from 'react';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types';
import { pick } from 'ramda';
import { deleteTag, tagDeleteType } from '../reducers/tagDelete';
import { listTags } from '../reducers/tagsList';
const propTypes = {
tag: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
deleteTag: PropTypes.func,
listTags: PropTypes.func,
tagDelete: tagDeleteType,
};
export default function DeleteTagConfirmModal({ tag, toggle, isOpen }) {
return (
<Modal toggle={toggle} isOpen={isOpen} centered>
<ModalHeader toggle={toggle}>
<span className="text-danger">Delete tag</span>
</ModalHeader>
<ModalBody>
Are you sure you want to delete tag <b>{tag}</b>?
</ModalBody>
<ModalFooter>
<button className="btn btn-link" onClick={toggle}>Cancel</button>
<button className="btn btn-danger">Delete tag</button>
</ModalFooter>
</Modal>
);
export class DeleteTagConfirmModal extends Component {
doDelete = () => {
const { tag, toggle, deleteTag } = this.props;
deleteTag(tag).then(() => {
this.tagDeleted = true;
toggle();
}).catch(() => {});
};
onClosed = () => {
if (!this.tagDeleted) {
return;
}
this.props.listTags();
};
componentDidMount() {
this.tagDeleted = false;
}
render() {
const { tag, toggle, isOpen, tagDelete } = this.props;
return (
<Modal toggle={toggle} isOpen={isOpen} centered onClosed={this.onClosed}>
<ModalHeader toggle={toggle}>
<span className="text-danger">Delete tag</span>
</ModalHeader>
<ModalBody>
Are you sure you want to delete tag <b>{tag}</b>?
{tagDelete.error && (
<div className="p-2 mt-2 bg-danger text-white text-center">
Something went wrong while deleting the tag :(
</div>
)}
</ModalBody>
<ModalFooter>
<button className="btn btn-link" onClick={toggle}>Cancel</button>
<button
className="btn btn-danger"
onClick={this.doDelete}
disabled={tagDelete.deleting}
>
{tagDelete.deleting ? 'Deleting tag...' : 'Delete tag'}
</button>
</ModalFooter>
</Modal>
);
}
}
DeleteTagConfirmModal.propTypes = propTypes;
export default connect(
pick(['tagDelete']),
{ deleteTag, listTags }
)(DeleteTagConfirmModal);

View file

@ -0,0 +1,52 @@
import ShlinkApiClient from '../../api/ShlinkApiClient';
import { curry } from 'ramda';
import PropTypes from 'prop-types';
const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START';
const DELETE_TAG_ERROR = 'shlink/deleteTag/DELETE_TAG_ERROR';
const DELETE_TAG = 'shlink/deleteTag/DELETE_TAG';
export const tagDeleteType = PropTypes.shape({
deleting: PropTypes.bool,
error: PropTypes.bool,
});
const defaultState = {
deleting: false,
error: false,
};
export default function reduce(state = defaultState, action) {
switch (action.type) {
case DELETE_TAG_START:
return {
deleting: true,
error: false,
};
case DELETE_TAG_ERROR:
return {
deleting: false,
error: true,
};
case DELETE_TAG:
return {
deleting: false,
error: false,
};
default:
return state;
}
}
export const _deleteTag = (ShlinkApiClient, tag) => async dispatch => {
dispatch({ type: DELETE_TAG_START });
try {
await ShlinkApiClient.deleteTags([tag]);
dispatch({ type: DELETE_TAG });
} catch (e) {
dispatch({ type: DELETE_TAG_ERROR });
throw e;
}
};
export const deleteTag = curry(_deleteTag)(ShlinkApiClient);

View file

@ -43,7 +43,6 @@ export const _listTags = ShlinkApiClient => async dispatch => {
dispatch({ tags, type: LIST_TAGS });
} catch (e) {
dispatch({ type: LIST_TAGS_ERROR });
throw e;
}
};
export const listTags = () => _listTags(ShlinkApiClient);