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); };