From 1dee4782345189d2d37c5d7b0d9a89685f6cc98b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 18 Aug 2018 16:39:47 +0200 Subject: [PATCH] Implemented delete tag behavior --- src/api/ShlinkApiClient.js | 11 +++- src/reducers/index.js | 2 + src/tags/TagsList.js | 6 +- src/tags/helpers/DeleteTagConfirmModal.js | 78 ++++++++++++++++++----- src/tags/reducers/tagDelete.js | 52 +++++++++++++++ src/tags/reducers/tagsList.js | 1 - 6 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 src/tags/reducers/tagDelete.js diff --git a/src/api/ShlinkApiClient.js b/src/api/ShlinkApiClient.js index 7dac876e..767fc742 100644 --- a/src/api/ShlinkApiClient.js +++ b/src/api/ShlinkApiClient.js @@ -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 diff --git a/src/reducers/index.js b/src/reducers/index.js index 96c904b1..84587ae5 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -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, }); diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js index b6b5e3b3..1ce60416 100644 --- a/src/tags/TagsList.js +++ b/src/tags/TagsList.js @@ -22,7 +22,11 @@ export class TagsList extends React.Component { } if (tagsList.error) { - return
Error loading tags :(
; + return ( +
+
Error loading tags :(
+
+ ); } const tagsCount = tagsList.tags.length; diff --git a/src/tags/helpers/DeleteTagConfirmModal.js b/src/tags/helpers/DeleteTagConfirmModal.js index b4df93ad..e4138df3 100644 --- a/src/tags/helpers/DeleteTagConfirmModal.js +++ b/src/tags/helpers/DeleteTagConfirmModal.js @@ -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 ( - - - Delete tag - - - Are you sure you want to delete tag {tag}? - - - - - - - ); +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 ( + + + Delete tag + + + Are you sure you want to delete tag {tag}? + {tagDelete.error && ( +
+ Something went wrong while deleting the tag :( +
+ )} +
+ + + + +
+ ); + } } DeleteTagConfirmModal.propTypes = propTypes; + +export default connect( + pick(['tagDelete']), + { deleteTag, listTags } +)(DeleteTagConfirmModal); diff --git a/src/tags/reducers/tagDelete.js b/src/tags/reducers/tagDelete.js new file mode 100644 index 00000000..373c1b2d --- /dev/null +++ b/src/tags/reducers/tagDelete.js @@ -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); diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js index c5fe5ab0..d0229ec2 100644 --- a/src/tags/reducers/tagsList.js +++ b/src/tags/reducers/tagsList.js @@ -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);