mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 17:57:26 +03:00
Implemented short URLs deletion
This commit is contained in:
parent
2d6dda3576
commit
f2d03203ae
8 changed files with 150 additions and 11 deletions
|
@ -44,6 +44,11 @@ export class ShlinkApiClient {
|
||||||
.then((resp) => resp.data)
|
.then((resp) => resp.data)
|
||||||
.catch((e) => this._handleAuthError(e, this.getShortUrl, [ shortCode ]));
|
.catch((e) => this._handleAuthError(e, this.getShortUrl, [ shortCode ]));
|
||||||
|
|
||||||
|
deleteShortUrl = (shortCode) =>
|
||||||
|
this._performRequest(`/short-codes/${shortCode}`, 'DELETE')
|
||||||
|
.then(() => ({}))
|
||||||
|
.catch((e) => this._handleAuthError(e, this.deleteShortUrl, [ shortCode ]));
|
||||||
|
|
||||||
updateShortUrlTags = (shortCode, tags) =>
|
updateShortUrlTags = (shortCode, tags) =>
|
||||||
this._performRequest(`/short-codes/${shortCode}/tags`, 'PUT', {}, { tags })
|
this._performRequest(`/short-codes/${shortCode}/tags`, 'PUT', {}, { tags })
|
||||||
.then((resp) => resp.data.tags)
|
.then((resp) => resp.data.tags)
|
||||||
|
|
|
@ -3,7 +3,8 @@ import serversReducer from '../servers/reducers/server';
|
||||||
import selectedServerReducer from '../servers/reducers/selectedServer';
|
import selectedServerReducer from '../servers/reducers/selectedServer';
|
||||||
import shortUrlsListReducer from '../short-urls/reducers/shortUrlsList';
|
import shortUrlsListReducer from '../short-urls/reducers/shortUrlsList';
|
||||||
import shortUrlsListParamsReducer from '../short-urls/reducers/shortUrlsListParams';
|
import shortUrlsListParamsReducer from '../short-urls/reducers/shortUrlsListParams';
|
||||||
import shortUrlCreationResultReducer from '../short-urls/reducers/shortUrlCreationResult';
|
import shortUrlCreationReducer from '../short-urls/reducers/shortUrlCreation';
|
||||||
|
import shortUrlDeletionReducer from '../short-urls/reducers/shortUrlDeletion';
|
||||||
import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags';
|
import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags';
|
||||||
import shortUrlVisitsReducer from '../visits/reducers/shortUrlVisits';
|
import shortUrlVisitsReducer from '../visits/reducers/shortUrlVisits';
|
||||||
import shortUrlDetailReducer from '../visits/reducers/shortUrlDetail';
|
import shortUrlDetailReducer from '../visits/reducers/shortUrlDetail';
|
||||||
|
@ -16,7 +17,8 @@ export default combineReducers({
|
||||||
selectedServer: selectedServerReducer,
|
selectedServer: selectedServerReducer,
|
||||||
shortUrlsList: shortUrlsListReducer,
|
shortUrlsList: shortUrlsListReducer,
|
||||||
shortUrlsListParams: shortUrlsListParamsReducer,
|
shortUrlsListParams: shortUrlsListParamsReducer,
|
||||||
shortUrlCreationResult: shortUrlCreationResultReducer,
|
shortUrlCreationResult: shortUrlCreationReducer,
|
||||||
|
shortUrlDeletion: shortUrlDeletionReducer,
|
||||||
shortUrlTags: shortUrlTagsReducer,
|
shortUrlTags: shortUrlTagsReducer,
|
||||||
shortUrlVisits: shortUrlVisitsReducer,
|
shortUrlVisits: shortUrlVisitsReducer,
|
||||||
shortUrlDetail: shortUrlDetailReducer,
|
shortUrlDetail: shortUrlDetailReducer,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Collapse } from 'reactstrap';
|
||||||
import DateInput from '../common/DateInput';
|
import DateInput from '../common/DateInput';
|
||||||
import TagsSelector from '../tags/helpers/TagsSelector';
|
import TagsSelector from '../tags/helpers/TagsSelector';
|
||||||
import CreateShortUrlResult from './helpers/CreateShortUrlResult';
|
import CreateShortUrlResult from './helpers/CreateShortUrlResult';
|
||||||
import { createShortUrl, resetCreateShortUrl } from './reducers/shortUrlCreationResult';
|
import { createShortUrl, resetCreateShortUrl } from './reducers/shortUrlCreation';
|
||||||
|
|
||||||
export class CreateShortUrlComponent extends React.Component {
|
export class CreateShortUrlComponent extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
import { Card, CardBody, Tooltip } from 'reactstrap';
|
import { Card, CardBody, Tooltip } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { createShortUrlResultType } from '../reducers/shortUrlCreationResult';
|
import { createShortUrlResultType } from '../reducers/shortUrlCreation';
|
||||||
import { stateFlagTimeout } from '../../utils/utils';
|
import { stateFlagTimeout } from '../../utils/utils';
|
||||||
import './CreateShortUrlResult.scss';
|
import './CreateShortUrlResult.scss';
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,56 @@
|
||||||
import React, { Component } 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, identity } from 'ramda';
|
||||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||||
|
import {
|
||||||
|
deleteShortUrl,
|
||||||
|
resetDeleteShortUrl,
|
||||||
|
shortUrlDeleted,
|
||||||
|
shortUrlDeletionType,
|
||||||
|
} from '../reducers/shortUrlDeletion';
|
||||||
import './QrCodeModal.scss';
|
import './QrCodeModal.scss';
|
||||||
|
|
||||||
export default class DeleteShortUrlModal extends Component {
|
export class DeleteShortUrlModalComponent extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
shortUrl: shortUrlType,
|
shortUrl: shortUrlType,
|
||||||
toggle: PropTypes.func,
|
toggle: PropTypes.func,
|
||||||
isOpen: PropTypes.bool,
|
isOpen: PropTypes.bool,
|
||||||
|
shortUrlDeletion: shortUrlDeletionType,
|
||||||
|
deleteShortUrl: PropTypes.func,
|
||||||
|
resetDeleteShortUrl: PropTypes.func,
|
||||||
|
shortUrlDeleted: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = { inputValue: '' };
|
state = { inputValue: '' };
|
||||||
|
handleDeleteUrl = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const { deleteShortUrl, shortUrl, toggle, shortUrlDeleted } = this.props;
|
||||||
|
const { shortCode } = shortUrl;
|
||||||
|
|
||||||
|
deleteShortUrl(shortCode)
|
||||||
|
.then(() => {
|
||||||
|
shortUrlDeleted(shortCode);
|
||||||
|
toggle();
|
||||||
|
})
|
||||||
|
.catch(identity);
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const { resetDeleteShortUrl } = this.props;
|
||||||
|
|
||||||
|
resetDeleteShortUrl();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { shortUrl, toggle, isOpen } = this.props;
|
const { shortUrl, toggle, isOpen, shortUrlDeletion } = this.props;
|
||||||
|
const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} toggle={toggle} centered>
|
<Modal isOpen={isOpen} toggle={toggle} centered>
|
||||||
<form onSubmit={(e) => e.preventDefault()}>
|
<form onSubmit={this.handleDeleteUrl}>
|
||||||
<ModalHeader toggle={toggle}>
|
<ModalHeader toggle={toggle}>
|
||||||
<span className="text-danger">Delete short URL</span>
|
<span className="text-danger">Delete short URL</span>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
@ -33,16 +65,26 @@ export default class DeleteShortUrlModal extends Component {
|
||||||
value={this.state.inputValue}
|
value={this.state.inputValue}
|
||||||
onChange={(e) => this.setState({ inputValue: e.target.value })}
|
onChange={(e) => this.setState({ inputValue: e.target.value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{shortUrlDeletion.error && shortUrlDeletion.errorData.error === THRESHOLD_REACHED && (
|
||||||
|
<div className="p-2 mt-2 bg-warning text-center">
|
||||||
|
This short URL has received too many visits and therefore, it cannot be deleted
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{shortUrlDeletion.error && shortUrlDeletion.errorData.error !== THRESHOLD_REACHED && (
|
||||||
|
<div className="p-2 mt-2 bg-danger text-white text-center">
|
||||||
|
Something went wrong while deleting the URL :(
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<button type="button" className="btn btn-link" onClick={toggle}>Cancel</button>
|
<button type="button" className="btn btn-link" onClick={toggle}>Cancel</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
disabled={this.state.inputValue !== shortUrl.shortCode}
|
disabled={this.state.inputValue !== shortUrl.shortCode || shortUrlDeletion.loading}
|
||||||
onClick={toggle}
|
|
||||||
>
|
>
|
||||||
Delete
|
{shortUrlDeletion.loading ? 'Deleting...' : 'Delete'}
|
||||||
</button>
|
</button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
@ -50,3 +92,10 @@ export default class DeleteShortUrlModal extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DeleteShortUrlModal = connect(
|
||||||
|
pick([ 'shortUrlDeletion' ]),
|
||||||
|
{ deleteShortUrl, resetDeleteShortUrl, shortUrlDeleted }
|
||||||
|
)(DeleteShortUrlModalComponent);
|
||||||
|
|
||||||
|
export default DeleteShortUrlModal;
|
||||||
|
|
76
src/short-urls/reducers/shortUrlDeletion.js
Normal file
76
src/short-urls/reducers/shortUrlDeletion.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { curry } from 'ramda';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import shlinkApiClient from '../../api/ShlinkApiClient';
|
||||||
|
|
||||||
|
/* eslint-disable padding-line-between-statements, newline-after-var */
|
||||||
|
const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START';
|
||||||
|
const DELETE_SHORT_URL_ERROR = 'shlink/deleteShortUrl/DELETE_SHORT_URL_ERROR';
|
||||||
|
const DELETE_SHORT_URL = 'shlink/deleteShortUrl/DELETE_SHORT_URL';
|
||||||
|
const RESET_DELETE_SHORT_URL = 'shlink/deleteShortUrl/RESET_DELETE_SHORT_URL';
|
||||||
|
export const SHORT_URL_DELETED = 'shlink/deleteShortUrl/SHORT_URL_DELETED';
|
||||||
|
/* eslint-enable padding-line-between-statements, newline-after-var */
|
||||||
|
|
||||||
|
export const shortUrlDeletionType = PropTypes.shape({
|
||||||
|
shortCode: PropTypes.string.isRequired,
|
||||||
|
loading: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.bool.isRequired,
|
||||||
|
errorData: PropTypes.shape({
|
||||||
|
error: PropTypes.string,
|
||||||
|
message: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
shortCode: '',
|
||||||
|
loading: false,
|
||||||
|
error: false,
|
||||||
|
errorData: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case DELETE_SHORT_URL_START:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
error: false,
|
||||||
|
};
|
||||||
|
case DELETE_SHORT_URL_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
error: true,
|
||||||
|
errorData: action.errorData,
|
||||||
|
};
|
||||||
|
case DELETE_SHORT_URL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
shortCode: action.shortCode,
|
||||||
|
loading: false,
|
||||||
|
error: false,
|
||||||
|
};
|
||||||
|
case RESET_DELETE_SHORT_URL:
|
||||||
|
return defaultState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _deleteShortUrl = (shlinkApiClient, shortCode) => async (dispatch) => {
|
||||||
|
dispatch({ type: DELETE_SHORT_URL_START });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await shlinkApiClient.deleteShortUrl(shortCode);
|
||||||
|
dispatch({ type: DELETE_SHORT_URL, shortCode });
|
||||||
|
} catch (e) {
|
||||||
|
dispatch({ type: DELETE_SHORT_URL_ERROR, errorData: e.response.data });
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteShortUrl = curry(_deleteShortUrl)(shlinkApiClient);
|
||||||
|
|
||||||
|
export const resetDeleteShortUrl = () => ({ type: RESET_DELETE_SHORT_URL });
|
||||||
|
|
||||||
|
export const shortUrlDeleted = (shortCode) => ({ type: SHORT_URL_DELETED, shortCode });
|
|
@ -1,7 +1,8 @@
|
||||||
import { assoc, assocPath } from 'ramda';
|
import { assoc, assocPath, reject } from 'ramda';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import shlinkApiClient from '../../api/ShlinkApiClient';
|
import shlinkApiClient from '../../api/ShlinkApiClient';
|
||||||
import { SHORT_URL_TAGS_EDITED } from './shortUrlTags';
|
import { SHORT_URL_TAGS_EDITED } from './shortUrlTags';
|
||||||
|
import { SHORT_URL_DELETED } from './shortUrlDeletion';
|
||||||
|
|
||||||
/* eslint-disable padding-line-between-statements, newline-after-var */
|
/* eslint-disable padding-line-between-statements, newline-after-var */
|
||||||
const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';
|
const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';
|
||||||
|
@ -43,6 +44,12 @@ export default function reducer(state = initialState, action) {
|
||||||
shortUrl.shortCode === action.shortCode
|
shortUrl.shortCode === action.shortCode
|
||||||
? assoc('tags', action.tags, shortUrl)
|
? assoc('tags', action.tags, shortUrl)
|
||||||
: shortUrl), state);
|
: shortUrl), state);
|
||||||
|
case SHORT_URL_DELETED:
|
||||||
|
return assocPath(
|
||||||
|
[ 'shortUrls', 'data' ],
|
||||||
|
reject((shortUrl) => shortUrl.shortCode === action.shortCode, state.shortUrls.data),
|
||||||
|
state,
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue