diff --git a/package.json b/package.json
index b1582502..ee0b7d7e 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/common/Home.js b/src/common/Home.js
index 7cb44dc8..f021de55 100644
--- a/src/common/Home.js
+++ b/src/common/Home.js
@@ -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 (
Welcome to Shlink
- {hasServers && Please, select a server.}
- {!hasServers && Please, add a server.}
+ {!loading && hasServers && Please, select a server.}
+ {!loading && !hasServers && Please, add a server.}
+ {loading && Trying to load servers....}
- {hasServers && (
+ {!loading && hasServers && (
{servers.map(({ name, id }) => (
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 Trying to load servers...;
+ }
if (isEmpty(servers)) {
return Add a server first...;
@@ -22,7 +27,7 @@ const ServersDropdown = (serversExporter) => class ServersDropdown extends React
return (
- {values(servers).map(({ name, id }) => (
+ {servers.map(({ name, id }) => (
class ServersDropdown extends React
);
};
- componentDidMount() {
- this.props.listServers();
- }
+ componentDidMount = this.props.listServers;
- render() {
- return (
-
- Servers
- {this.renderServers()}
-
- );
- }
+ render = () => (
+
+ Servers
+ {this.renderServers()}
+
+ );
};
export default ServersDropdown;
diff --git a/src/servers/helpers/ImportServersBtn.js b/src/servers/helpers/ImportServersBtn.js
index 4d687976..ec56cb9e 100644
--- a/src/servers/helpers/ImportServersBtn.js
+++ b/src/servers/helpers/ImportServersBtn.js
@@ -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(() => {
diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js
index 2d35987f..bd14e80f 100644
--- a/src/servers/reducers/selectedServer.js
+++ b/src/servers/reducers/selectedServer.js
@@ -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,
diff --git a/src/servers/reducers/server.js b/src/servers/reducers/server.js
index 1a3955f6..a5d3b3b4 100644
--- a/src/servers/reducers/server.js
+++ b/src/servers/reducers/server.js
@@ -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
+);
diff --git a/src/servers/services/ServersImporter.js b/src/servers/services/ServersImporter.js
index af855a64..35ce8ef9 100644
--- a/src/servers/services/ServersImporter.js
+++ b/src/servers/services/ServersImporter.js
@@ -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;
diff --git a/src/servers/services/ServersService.js b/src/servers/services/ServersService.js
index 52bb8fed..aee850d5 100644
--- a/src/servers/services/ServersService.js
+++ b/src/servers/services/ServersService.js
@@ -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()));
}
diff --git a/src/servers/services/provideServices.js b/src/servers/services/provideServices.js
index 7821e168..0b135491 100644
--- a/src/servers/services/provideServices.js
+++ b/src/servers/services/provideServices.js
@@ -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);
};