Connected creation form with redux, and created reducer for short URL creation

This commit is contained in:
Alejandro Celaya 2018-07-28 10:41:05 +02:00
parent c51bf5b9a0
commit 0a5c20e3ee
13 changed files with 116 additions and 48 deletions

View file

@ -2,6 +2,8 @@ import axios from 'axios';
import { isEmpty } from 'ramda'; import { isEmpty } from 'ramda';
import qs from 'qs'; import qs from 'qs';
const API_VERSION = '1';
export class ShlinkApiClient { export class ShlinkApiClient {
constructor(axios) { constructor(axios) {
this.axios = axios; this.axios = axios;
@ -16,7 +18,7 @@ export class ShlinkApiClient {
* @param {String} apiKey * @param {String} apiKey
*/ */
setConfig = ({ url, apiKey }) => { setConfig = ({ url, apiKey }) => {
this._baseUrl = url; this._baseUrl = `${url}/rest/v${API_VERSION}`;
this._apiKey = apiKey; this._apiKey = apiKey;
}; };
@ -26,11 +28,18 @@ export class ShlinkApiClient {
* @returns {Promise<Array>} * @returns {Promise<Array>}
*/ */
listShortUrls = (options = {}) => { listShortUrls = (options = {}) => {
return this._performRequest('/rest/short-codes', 'GET', options) return this._performRequest('/short-codes', 'GET', options)
.then(resp => resp.data.shortUrls) .then(resp => resp.data.shortUrls)
.catch(e => this._handleAuthError(e, this.listShortUrls, [options])); .catch(e => this._handleAuthError(e, this.listShortUrls, [options]));
}; };
createShortUrl = options => {
console.log(options);
// this._performRequest('/short-codes', 'POST', options)
// .then(resp => resp.data)
// .catch(e => this._handleAuthError(e, this.listShortUrls, [options]));
};
_performRequest = async (url, method = 'GET', params = {}, data = {}) => { _performRequest = async (url, method = 'GET', params = {}, data = {}) => {
if (isEmpty(this._token)) { if (isEmpty(this._token)) {
this._token = await this._authenticate(); this._token = await this._authenticate();
@ -54,7 +63,7 @@ export class ShlinkApiClient {
_authenticate = async () => { _authenticate = async () => {
const resp = await this.axios({ const resp = await this.axios({
method: 'POST', method: 'POST',
url: `${this._baseUrl}/rest/authenticate`, url: `${this._baseUrl}/authenticate`,
data: { apiKey: this._apiKey } data: { apiKey: this._apiKey }
}); });
return resp.data.token; return resp.data.token;

View file

@ -5,6 +5,7 @@ import { selectServer } from '../servers/reducers/selectedServer';
import CreateShortUrl from '../short-urls/CreateShortUrl'; import CreateShortUrl from '../short-urls/CreateShortUrl';
import ShortUrls from '../short-urls/ShortUrls'; import ShortUrls from '../short-urls/ShortUrls';
import AsideMenu from './AsideMenu'; import AsideMenu from './AsideMenu';
import { pick } from 'ramda';
export class MenuLayout extends React.Component { export class MenuLayout extends React.Component {
componentDidMount() { componentDidMount() {
@ -35,7 +36,4 @@ export class MenuLayout extends React.Component {
} }
} }
export default connect(state => ({ export default connect(pick(['selectedServer', 'shortUrlsListParams']), { selectServer })(MenuLayout);
selectedServer: state.selectedServer,
shortUrlsListParams: state.shortUrlsListParams,
}), { selectServer })(MenuLayout);

View file

@ -4,10 +4,12 @@ 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';
export default combineReducers({ export default combineReducers({
servers: serversReducer, servers: serversReducer,
selectedServer: selectedServerReducer, selectedServer: selectedServerReducer,
shortUrlsList: shortUrlsListReducer, shortUrlsList: shortUrlsListReducer,
shortUrlsListParams: shortUrlsListParamsReducer, shortUrlsListParams: shortUrlsListParamsReducer,
shortUrlCreationResult: shortUrlCreationResultReducer,
}); });

View file

@ -1,4 +1,4 @@
import { assoc } from 'ramda'; import { assoc, pick } from 'ramda';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createServer } from './reducers/server'; import { createServer } from './reducers/server';
@ -57,7 +57,4 @@ export class CreateServer extends React.Component {
} }
} }
export default connect(state => ({ selectedServer: state.selectedServer }), { export default connect(pick(['selectedServer']), {createServer, resetSelectedServer })(CreateServer);
createServer,
resetSelectedServer
})(CreateServer);

View file

@ -1,4 +1,4 @@
import { isEmpty } from 'ramda'; import { isEmpty, pick } from 'ramda';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -8,7 +8,7 @@ import { listServers } from './reducers/server';
export class ServersDropdown extends React.Component { export class ServersDropdown extends React.Component {
renderServers = () => { renderServers = () => {
const { servers, currentServer } = this.props; const { servers, selectedServer } = this.props;
if (isEmpty(servers)) { if (isEmpty(servers)) {
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem> return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>
@ -19,7 +19,7 @@ export class ServersDropdown extends React.Component {
<DropdownItem <DropdownItem
tag={Link} tag={Link}
to={`/server/${id}/list-short-urls/1`} to={`/server/${id}/list-short-urls/1`}
active={currentServer && currentServer.id === id} active={selectedServer && selectedServer.id === id}
> >
{name} {name}
</DropdownItem> </DropdownItem>
@ -41,9 +41,4 @@ export class ServersDropdown extends React.Component {
} }
} }
const mapStateToProps = state => ({ export default connect(pick(['servers', 'selectedServer']), { listServers })(ServersDropdown);
servers: state.servers,
currentServer: state.selectedServer,
});
export default connect(mapStateToProps, { listServers })(ServersDropdown);

View file

@ -2,15 +2,18 @@ import calendarIcon from '@fortawesome/fontawesome-free-regular/faCalendarAlt';
import downIcon from '@fortawesome/fontawesome-free-solid/faAngleDoubleDown'; import downIcon from '@fortawesome/fontawesome-free-solid/faAngleDoubleDown';
import upIcon from '@fortawesome/fontawesome-free-solid/faAngleDoubleUp'; import upIcon from '@fortawesome/fontawesome-free-solid/faAngleDoubleUp';
import FontAwesomeIcon from '@fortawesome/react-fontawesome'; import FontAwesomeIcon from '@fortawesome/react-fontawesome';
import { assoc, replace } from 'ramda'; import { assoc, replace, pick } from 'ramda';
import React from 'react'; import React from 'react';
import DatePicker from 'react-datepicker'; import DatePicker from 'react-datepicker';
import ReactTags from 'react-tag-autocomplete'; import ReactTags from 'react-tag-autocomplete';
import { Collapse } from 'reactstrap'; import { Collapse } from 'reactstrap';
import '../../node_modules/react-datepicker/dist/react-datepicker.css'; import '../../node_modules/react-datepicker/dist/react-datepicker.css';
import './CreateShortUrl.scss'; import './CreateShortUrl.scss';
import CreateShortUrlResult from './helpers/CreateShortUrlResult';
import { createShortUrl } from './reducers/shortUrlCreationResult';
import { connect } from 'react-redux';
export default class CreateShortUrl extends React.Component { export class CreateShortUrl extends React.Component {
state = { state = {
longUrl: '', longUrl: '',
tags: [], tags: [],
@ -49,10 +52,13 @@ export default class CreateShortUrl extends React.Component {
readOnly readOnly
{...props} {...props}
/>; />;
const save = e => {
e.preventDefault();
};
return ( return (
<div className="short-urls-container"> <div className="short-urls-container">
<form onSubmit={e => e.preventDefault()}> <form onSubmit={save}>
<div className="form-group"> <div className="form-group">
<input <input
className="form-control form-control-lg" className="form-control form-control-lg"
@ -109,8 +115,12 @@ export default class CreateShortUrl extends React.Component {
</button> </button>
<button className="btn btn-outline-primary create-short-url__btn float-right">Create</button> <button className="btn btn-outline-primary create-short-url__btn float-right">Create</button>
</div> </div>
<CreateShortUrlResult creationResult={this.props.shortUrlCreationResult} />
</form> </form>
</div> </div>
); );
} }
} }
export default connect(pick(['shortUrlCreationResult']), { createShortUrl })(CreateShortUrl);

View file

@ -1,9 +1,8 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
import { connect } from 'react-redux';
export class Paginator extends React.Component { export default class Paginator extends React.Component {
render() { render() {
const { paginator = {}, serverId } = this.props; const { paginator = {}, serverId } = this.props;
const { currentPage, pagesCount = 0 } = paginator; const { currentPage, pagesCount = 0 } = paginator;
@ -51,5 +50,3 @@ export class Paginator extends React.Component {
); );
} }
} }
export default connect()(Paginator);

View file

@ -4,6 +4,7 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { updateShortUrlsList } from './reducers/shortUrlsList'; import { updateShortUrlsList } from './reducers/shortUrlsList';
import './SearchBar.scss'; import './SearchBar.scss';
import { pick } from 'ramda';
export class SearchBar extends React.Component { export class SearchBar extends React.Component {
state = { state = {
@ -52,6 +53,4 @@ export class SearchBar extends React.Component {
} }
} }
export default connect(state => ( export default connect(pick(['shortUrlsListParams']), { updateShortUrlsList })(SearchBar);
{ shortUrlsListParams: state.shortUrlsListParams }
), { updateShortUrlsList })(SearchBar);

View file

@ -8,6 +8,7 @@ import Tag from '../utils/Tag';
import { ShortUrlsRow } from './helpers/ShortUrlsRow'; import { ShortUrlsRow } from './helpers/ShortUrlsRow';
import { listShortUrls } from './reducers/shortUrlsList'; import { listShortUrls } from './reducers/shortUrlsList';
import './ShortUrlsList.scss'; import './ShortUrlsList.scss';
import { pick } from 'ramda';
export class ShortUrlsList extends React.Component { export class ShortUrlsList extends React.Component {
refreshList = extraParams => { refreshList = extraParams => {
@ -122,7 +123,4 @@ export class ShortUrlsList extends React.Component {
} }
} }
export default connect(state => ({ export default connect(pick(['selectedServer', 'shortUrlsListParams']), { listShortUrls })(ShortUrlsList);
selectedServer: state.selectedServer,
shortUrlsListParams: state.shortUrlsListParams,
}), { listShortUrls })(ShortUrlsList);

View file

@ -0,0 +1,18 @@
import React from 'react';
import { isNil } from 'ramda';
export default function CreateShortUrlResult ({ creationResult }) {
if (creationResult.loading) {
return <div className="text-center">Loading...</div>
}
if (creationResult.error) {
return <div className="text-center color-danger">An error occurred while creating the URL :(</div>
}
if (isNil(creationResult.result)) {
return null;
}
return <div className="text-center">Great!</div>;
};

View file

@ -0,0 +1,46 @@
import ShlinkApiClient from '../../api/ShlinkApiClient';
const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START';
const CREATE_SHORT_URL_ERROR = 'shlink/createShortUrl/CREATE_SHORT_URL_ERROR';
const CREATE_SHORT_URL = 'shlink/createShortUrl/CREATE_SHORT_URL';
const defaultState = {
result: null,
saving: false,
error: false,
};
export default function reducer(state = defaultState, action) {
switch (action.type) {
case CREATE_SHORT_URL_START:
return {
...state,
saving: true,
};
case CREATE_SHORT_URL_ERROR:
return {
...state,
saving: false,
error: true,
};
case CREATE_SHORT_URL:
return {
result: action.result,
saving: false,
error: true,
};
default:
return state;
}
}
export const createShortUrl = data => async dispatch => {
dispatch({ type: CREATE_SHORT_URL_START });
try {
const result = await ShlinkApiClient.createShortUrl(data);
dispatch({ type: CREATE_SHORT_URL, result });
} catch (e) {
dispatch({ type: CREATE_SHORT_URL_ERROR });
}
};

View file

@ -41,15 +41,13 @@ export const listShortUrls = (serverId, params = {}) => {
return updateShortUrlsList(params); return updateShortUrlsList(params);
}; };
export const updateShortUrlsList = (params = {}) => { export const updateShortUrlsList = (params = {}) => async dispatch => {
return async dispatch => { dispatch({ type: LIST_SHORT_URLS_START });
dispatch({ type: LIST_SHORT_URLS_START });
try { try {
const shortUrls = await ShlinkApiClient.listShortUrls(params); const shortUrls = await ShlinkApiClient.listShortUrls(params);
dispatch({ type: LIST_SHORT_URLS, shortUrls, params }); dispatch({ type: LIST_SHORT_URLS, shortUrls, params });
} catch (e) { } catch (e) {
dispatch({ type: LIST_SHORT_URLS_ERROR, params }); dispatch({ type: LIST_SHORT_URLS_ERROR, params });
} }
};
}; };

View file

@ -1,9 +1,8 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import ColorGenerator from '../utils/ColorGenerator'; import ColorGenerator from '../utils/ColorGenerator';
import './Tag.scss'; import './Tag.scss';
export class Tag extends React.Component { export default class Tag extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.colorGenerator = props.ColorGenerator; this.colorGenerator = props.ColorGenerator;
@ -18,4 +17,6 @@ export class Tag extends React.Component {
} }
} }
export default connect(state => ({ ColorGenerator }))(Tag); Tag.defaultProps = {
ColorGenerator
};