Updated list servers action so that it tries to fetch servers from the servers.json file when no local servers are found

This commit is contained in:
Alejandro Celaya 2019-04-28 12:07:09 +02:00
parent 502c8a7e02
commit 20820c47d4
9 changed files with 74 additions and 48 deletions

View file

@ -3,7 +3,7 @@
"description": "A React-based progressive web application for shlink",
"version": "1.0.0",
"private": false,
"homepage": "https://shlink.io",
"homepage": "",
"scripts": {
"lint": "npm run lint:js && npm run lint:css",
"lint:js": "eslint src test scripts config",

View file

@ -18,18 +18,20 @@ export default class Home extends React.Component {
}
render() {
const servers = values(this.props.servers);
const { servers: { list, loading } } = this.props;
const servers = values(list);
const hasServers = !isEmpty(servers);
return (
<div className="home">
<h1 className="home__title">Welcome to Shlink</h1>
<h5 className="home__intro">
{hasServers && <span>Please, select a server.</span>}
{!hasServers && <span>Please, <Link to="/server/create">add a server</Link>.</span>}
{!loading && hasServers && <span>Please, select a server.</span>}
{!loading && !hasServers && <span>Please, <Link to="/server/create">add a server</Link>.</span>}
{loading && <span>Trying to load servers....</span>}
</h5>
{hasServers && (
{!loading && hasServers && (
<ListGroup className="home__servers-list">
{servers.map(({ name, id }) => (
<ListGroupItem

View file

@ -14,7 +14,12 @@ const ServersDropdown = (serversExporter) => class ServersDropdown extends React
};
renderServers = () => {
const { servers, selectedServer, selectServer } = this.props;
const { servers: { list, loading }, selectedServer, selectServer } = this.props;
const servers = values(list);
if (loading) {
return <DropdownItem disabled><i>Trying to load servers...</i></DropdownItem>;
}
if (isEmpty(servers)) {
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>;
@ -22,7 +27,7 @@ const ServersDropdown = (serversExporter) => class ServersDropdown extends React
return (
<React.Fragment>
{values(servers).map(({ name, id }) => (
{servers.map(({ name, id }) => (
<DropdownItem
key={id}
tag={Link}
@ -46,18 +51,14 @@ const ServersDropdown = (serversExporter) => class ServersDropdown extends React
);
};
componentDidMount() {
this.props.listServers();
}
componentDidMount = this.props.listServers;
render() {
return (
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>Servers</DropdownToggle>
<DropdownMenu right>{this.renderServers()}</DropdownMenu>
</UncontrolledDropdown>
);
}
render = () => (
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>Servers</DropdownToggle>
<DropdownMenu right>{this.renderServers()}</DropdownMenu>
</UncontrolledDropdown>
);
};
export default ServersDropdown;

View file

@ -1,7 +1,5 @@
import React from 'react';
import { UncontrolledTooltip } from 'reactstrap';
import { assoc, map } from 'ramda';
import { v4 as uuid } from 'uuid';
import PropTypes from 'prop-types';
const ImportServersBtn = (serversImporter) => class ImportServersBtn extends React.Component {
@ -22,10 +20,8 @@ const ImportServersBtn = (serversImporter) => class ImportServersBtn extends Rea
render() {
const { importServersFromFile } = serversImporter;
const { onImport, createServers } = this.props;
const assocId = (server) => assoc('id', uuid(), server);
const onChange = ({ target }) =>
importServersFromFile(target.files[0])
.then(map(assocId))
.then(createServers)
.then(onImport)
.then(() => {

View file

@ -10,10 +10,10 @@ const initialState = null;
export const resetSelectedServer = createAction(RESET_SELECTED_SERVER);
export const selectServer = (serversService) => (serverId) => (dispatch) => {
export const selectServer = ({ findServerById }) => (serverId) => (dispatch) => {
dispatch(resetShortUrlParams());
const selectedServer = serversService.findServerById(serverId);
const selectedServer = findServerById(serverId);
dispatch({
type: SELECT_SERVER,

View file

@ -1,16 +1,51 @@
import { createAction, handleActions } from 'redux-actions';
import { pipe } from 'ramda';
import { handleActions } from 'redux-actions';
import { pipe, isEmpty, assoc, map } from 'ramda';
import { v4 as uuid } from 'uuid';
import { homepage } from '../../../package.json';
/* eslint-disable padding-line-between-statements */
export const FETCH_SERVERS_START = 'shlink/servers/FETCH_SERVERS_START';
export const FETCH_SERVERS = 'shlink/servers/FETCH_SERVERS';
/* eslint-enable padding-line-between-statements */
export const listServers = ({ listServers }) => createAction(FETCH_SERVERS, () => listServers());
const initialState = {
list: {},
loading: false,
};
export const createServer = ({ createServer }, listServers) => pipe(createServer, listServers);
export const deleteServer = ({ deleteServer }, listServers) => pipe(deleteServer, listServers);
export const createServers = ({ createServers }, listServers) => pipe(createServers, listServers);
const assocId = (server) => assoc('id', uuid(), server);
export default handleActions({
[FETCH_SERVERS]: (state, { payload }) => payload,
}, {});
[FETCH_SERVERS_START]: (state) => ({ ...state, loading: true }),
[FETCH_SERVERS]: (state, { list }) => ({ list, loading: false }),
}, initialState);
export const listServers = ({ listServers, createServers }, { get }) => () => async (dispatch) => {
dispatch({ type: FETCH_SERVERS_START });
// Fetch list from local storage.
const localList = listServers();
if (!isEmpty(localList)) {
dispatch({ type: FETCH_SERVERS, list: localList });
return;
}
// If local list is empty, try to fetch it remotely, calculate IDs for every server, and use it
const { data: remoteList } = await get(`${homepage}/servers.json`);
const listWithIds = map(assocId, remoteList);
createServers(listWithIds);
dispatch({ type: FETCH_SERVERS, list: listWithIds });
};
export const createServer = ({ createServer }, listServersAction) => pipe(createServer, listServersAction);
export const deleteServer = ({ deleteServer }, listServersAction) => pipe(deleteServer, listServersAction);
export const createServers = ({ createServers }, listServersAction) => pipe(
map(assocId),
createServers,
listServersAction
);

View file

@ -1,9 +1,3 @@
import PropTypes from 'prop-types';
export const serversImporterType = PropTypes.shape({
importServersFromFile: PropTypes.func,
});
export default class ServersImporter {
constructor(csvjson) {
this.csvjson = csvjson;

View file

@ -1,10 +1,11 @@
import { assoc, dissoc, reduce } from 'ramda';
import { assoc, curry, dissoc, reduce } from 'ramda';
const SERVERS_STORAGE_KEY = 'servers';
export default class ServersService {
constructor(storage) {
this.storage = storage;
this.setServers = curry(this.storage.set)(SERVERS_STORAGE_KEY);
}
listServers = () => this.storage.get(SERVERS_STORAGE_KEY) || {};
@ -20,12 +21,9 @@ export default class ServersService {
servers
);
this.storage.set(SERVERS_STORAGE_KEY, allServers);
this.setServers(allServers);
};
deleteServer = (server) =>
this.storage.set(
SERVERS_STORAGE_KEY,
dissoc(server.id, this.listServers())
);
deleteServer = ({ id }) =>
this.setServers(dissoc(id, this.listServers()));
}

View file

@ -38,7 +38,7 @@ const provideServices = (bottle, connect, withRouter) => {
bottle.serviceFactory('createServer', createServer, 'ServersService', 'listServers');
bottle.serviceFactory('createServers', createServers, 'ServersService', 'listServers');
bottle.serviceFactory('deleteServer', deleteServer, 'ServersService', 'listServers');
bottle.serviceFactory('listServers', listServers, 'ServersService');
bottle.serviceFactory('listServers', listServers, 'ServersService', 'axios');
bottle.serviceFactory('resetSelectedServer', () => resetSelectedServer);
};