mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 23:07:26 +03:00
Implemented delete tag behavior
This commit is contained in:
parent
f480e34f67
commit
1dee478234
6 changed files with 129 additions and 21 deletions
|
@ -54,7 +54,12 @@ export class ShlinkApiClient {
|
||||||
.then(resp => resp.data.tags.data)
|
.then(resp => resp.data.tags.data)
|
||||||
.catch(e => this._handleAuthError(e, this.listTags, []));
|
.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)) {
|
if (isEmpty(this._token)) {
|
||||||
this._token = await this._authenticate();
|
this._token = await this._authenticate();
|
||||||
}
|
}
|
||||||
|
@ -63,8 +68,8 @@ export class ShlinkApiClient {
|
||||||
method,
|
method,
|
||||||
url: `${this._baseUrl}${url}`,
|
url: `${this._baseUrl}${url}`,
|
||||||
headers: { 'Authorization': `Bearer ${this._token}` },
|
headers: { 'Authorization': `Bearer ${this._token}` },
|
||||||
params,
|
params: query,
|
||||||
data,
|
data: body,
|
||||||
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'brackets' })
|
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'brackets' })
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
// Save new token
|
// Save new token
|
||||||
|
|
|
@ -8,6 +8,7 @@ import shortUrlCreationResultReducer from '../short-urls/reducers/shortUrlCreati
|
||||||
import shortUrlVisitsReducer from '../short-urls/reducers/shortUrlVisits';
|
import shortUrlVisitsReducer from '../short-urls/reducers/shortUrlVisits';
|
||||||
import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags';
|
import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags';
|
||||||
import tagsListReducer from '../tags/reducers/tagsList';
|
import tagsListReducer from '../tags/reducers/tagsList';
|
||||||
|
import tagDeleteReducer from '../tags/reducers/tagDelete';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
servers: serversReducer,
|
servers: serversReducer,
|
||||||
|
@ -18,4 +19,5 @@ export default combineReducers({
|
||||||
shortUrlVisits: shortUrlVisitsReducer,
|
shortUrlVisits: shortUrlVisitsReducer,
|
||||||
shortUrlTags: shortUrlTagsReducer,
|
shortUrlTags: shortUrlTagsReducer,
|
||||||
tagsList: tagsListReducer,
|
tagsList: tagsListReducer,
|
||||||
|
tagDelete: tagDeleteReducer,
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,11 @@ export class TagsList extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagsList.error) {
|
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;
|
const tagsCount = tagsList.tags.length;
|
||||||
|
|
|
@ -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 { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { pick } from 'ramda';
|
||||||
|
import { deleteTag, tagDeleteType } from '../reducers/tagDelete';
|
||||||
|
import { listTags } from '../reducers/tagsList';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
tag: PropTypes.string.isRequired,
|
tag: PropTypes.string.isRequired,
|
||||||
toggle: PropTypes.func.isRequired,
|
toggle: PropTypes.func.isRequired,
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
deleteTag: PropTypes.func,
|
||||||
|
listTags: PropTypes.func,
|
||||||
|
tagDelete: tagDeleteType,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DeleteTagConfirmModal({ tag, toggle, isOpen }) {
|
export class DeleteTagConfirmModal extends Component {
|
||||||
return (
|
doDelete = () => {
|
||||||
<Modal toggle={toggle} isOpen={isOpen} centered>
|
const { tag, toggle, deleteTag } = this.props;
|
||||||
<ModalHeader toggle={toggle}>
|
deleteTag(tag).then(() => {
|
||||||
<span className="text-danger">Delete tag</span>
|
this.tagDeleted = true;
|
||||||
</ModalHeader>
|
toggle();
|
||||||
<ModalBody>
|
}).catch(() => {});
|
||||||
Are you sure you want to delete tag <b>{tag}</b>?
|
};
|
||||||
</ModalBody>
|
onClosed = () => {
|
||||||
<ModalFooter>
|
if (!this.tagDeleted) {
|
||||||
<button className="btn btn-link" onClick={toggle}>Cancel</button>
|
return;
|
||||||
<button className="btn btn-danger">Delete tag</button>
|
}
|
||||||
</ModalFooter>
|
|
||||||
</Modal>
|
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;
|
DeleteTagConfirmModal.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
pick(['tagDelete']),
|
||||||
|
{ deleteTag, listTags }
|
||||||
|
)(DeleteTagConfirmModal);
|
||||||
|
|
52
src/tags/reducers/tagDelete.js
Normal file
52
src/tags/reducers/tagDelete.js
Normal 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);
|
|
@ -43,7 +43,6 @@ export const _listTags = ShlinkApiClient => async dispatch => {
|
||||||
dispatch({ tags, type: LIST_TAGS });
|
dispatch({ tags, type: LIST_TAGS });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch({ type: LIST_TAGS_ERROR });
|
dispatch({ type: LIST_TAGS_ERROR });
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const listTags = () => _listTags(ShlinkApiClient);
|
export const listTags = () => _listTags(ShlinkApiClient);
|
||||||
|
|
Loading…
Reference in a new issue