diff --git a/src/common/Home.js b/src/common/Home.js
index e670e20e..79bdc57a 100644
--- a/src/common/Home.js
+++ b/src/common/Home.js
@@ -1,11 +1,9 @@
-import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEmpty, values } from 'ramda';
import React from 'react';
import { Link } from 'react-router-dom';
-import { ListGroup, ListGroupItem } from 'reactstrap';
import PropTypes from 'prop-types';
import './Home.scss';
+import ServersListGroup from '../servers/ServersListGroup';
export default class Home extends React.Component {
static propTypes = {
@@ -25,27 +23,11 @@ export default class Home extends React.Component {
return (
Welcome to Shlink
-
+
{!loading && hasServers && Please, select a server.}
{!loading && !hasServers && Please, add a server.}
{loading && Trying to load servers...}
-
-
- {!loading && hasServers && (
-
- {servers.map(({ name, id }) => (
-
- {name}
-
-
- ))}
-
- )}
+
);
}
diff --git a/src/common/Home.scss b/src/common/Home.scss
index a57f033b..ee360009 100644
--- a/src/common/Home.scss
+++ b/src/common/Home.scss
@@ -1,5 +1,4 @@
@import '../utils/base';
-@import '../utils/mixins/vertical-align';
.home {
text-align: center;
@@ -17,21 +16,3 @@
font-size: 2.2rem;
}
}
-
-.home__servers-list {
- margin-top: 1rem;
- width: 100%;
- max-width: 400px;
-}
-
-.home__servers-item.home__servers-item {
- text-align: left;
- position: relative;
- padding: .75rem 2.5rem .75rem 1rem;
-}
-
-.home__servers-item-icon {
- @include vertical-align();
-
- right: 1rem;
-}
diff --git a/src/common/MenuLayout.js b/src/common/MenuLayout.js
index 6b5f5057..7a0a6ca7 100644
--- a/src/common/MenuLayout.js
+++ b/src/common/MenuLayout.js
@@ -6,7 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';
import * as PropTypes from 'prop-types';
import { serverType } from '../servers/prop-types';
-import MutedMessage from '../utils/MutedMessage';
+import Message from '../utils/Message';
import NotFound from './NotFound';
import './MenuLayout.scss';
@@ -28,19 +28,19 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
useEffect(() => setShowSidebar(false), [ location ]);
if (!selectedServer) {
- return ;
+ return ;
}
if (selectedServer.serverNotFound) {
- return Could not find a server with id "{serverId}" in this host.;
+ return Could not find this Shlink server in this host.;
}
if (selectedServer.serverNotReachable) {
return (
-
- Oops! Could not connect to Shlink server with ID "{serverId}". Make sure you have internet
- connection, the server is properly configured and it is on-line.
-
+
+ Oops! Could not connect to this Shlink server.
+ Make sure you have internet connection, and the server is properly configured and on-line.
+
);
}
diff --git a/src/servers/ServersListGroup.js b/src/servers/ServersListGroup.js
new file mode 100644
index 00000000..764ca583
--- /dev/null
+++ b/src/servers/ServersListGroup.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { ListGroup, ListGroupItem } from 'reactstrap';
+import { Link } from 'react-router-dom';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons';
+import { serverType } from './prop-types';
+import './ServersListGroup.scss';
+
+const propTypes = {
+ servers: PropTypes.arrayOf(serverType).isRequired,
+ children: PropTypes.node.isRequired,
+};
+
+const ServerListItem = ({ id, name }) => (
+
+ {name}
+
+
+);
+
+ServerListItem.propTypes = {
+ id: PropTypes.string,
+ name: PropTypes.string,
+};
+
+const ServersListGroup = ({ servers, children }) => (
+
+ {children}
+ {servers.length && (
+
+ {servers.map(({ id, name }) => )}
+
+ )}
+
+);
+
+ServersListGroup.propTypes = propTypes;
+
+export default ServersListGroup;
diff --git a/src/servers/ServersListGroup.scss b/src/servers/ServersListGroup.scss
new file mode 100644
index 00000000..2b33ab3f
--- /dev/null
+++ b/src/servers/ServersListGroup.scss
@@ -0,0 +1,18 @@
+@import '../utils/mixins/vertical-align';
+
+.servers-list__list-group {
+ margin-top: 1rem;
+ width: 100%;
+ max-width: 400px;
+}
+
+.servers-list__server-item.servers-list__server-item {
+ text-align: left;
+ position: relative;
+ padding: .75rem 2.5rem .75rem 1rem;
+}
+
+.servers-list__server-item-icon {
+ @include vertical-align();
+ right: 1rem;
+}
diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js
index 9cee6953..914a1ba8 100644
--- a/src/tags/TagsList.js
+++ b/src/tags/TagsList.js
@@ -1,7 +1,7 @@
import React from 'react';
import { splitEvery } from 'ramda';
import PropTypes from 'prop-types';
-import MutedMessage from '../utils/MutedMessage';
+import Message from '../utils/Message';
import SearchField from '../utils/SearchField';
const { ceil } = Math;
@@ -29,7 +29,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
const { tagsList, match } = this.props;
if (tagsList.loading) {
- return ;
+ return ;
}
if (tagsList.error) {
@@ -43,7 +43,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
const tagsCount = tagsList.filteredTags.length;
if (tagsCount < 1) {
- return No tags found;
+ return No tags found;
}
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags);
diff --git a/src/utils/MutedMessage.js b/src/utils/Message.js
similarity index 53%
rename from src/utils/MutedMessage.js
rename to src/utils/Message.js
index 61e5c90c..368141c6 100644
--- a/src/utils/MutedMessage.js
+++ b/src/utils/Message.js
@@ -5,21 +5,35 @@ import PropTypes from 'prop-types';
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+const getClassForType = (type) => {
+ const map = {
+ error: 'bg-danger',
+ };
+
+ return map[type] || 'bg-light';
+};
+const getTextClassForType = (type) => {
+ const map = {
+ error: 'text-white',
+ };
+
+ return map[type] || 'text-muted';
+};
+
const propTypes = {
noMargin: PropTypes.bool,
loading: PropTypes.bool,
children: PropTypes.node,
+ type: PropTypes.oneOf([ 'default', 'error' ]),
};
-const MutedMessage = ({ children, loading = false, noMargin = false }) => {
- const cardClasses = classNames('bg-light', {
- 'mt-4': !noMargin,
- });
+const Message = ({ children, loading = false, noMargin = false, type = 'default' }) => {
+ const cardClasses = classNames(getClassForType(type), { 'mt-4': !noMargin });
return (
-
+
{loading && }
{loading && !children && Loading...}
{children}
@@ -29,6 +43,6 @@ const MutedMessage = ({ children, loading = false, noMargin = false }) => {
);
};
-MutedMessage.propTypes = propTypes;
+Message.propTypes = propTypes;
-export default MutedMessage;
+export default Message;
diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js
index a0875b0b..7330cc12 100644
--- a/src/visits/ShortUrlVisits.js
+++ b/src/visits/ShortUrlVisits.js
@@ -4,7 +4,7 @@ import { Card } from 'reactstrap';
import PropTypes from 'prop-types';
import qs from 'qs';
import DateRangeRow from '../utils/DateRangeRow';
-import MutedMessage from '../utils/MutedMessage';
+import Message from '../utils/Message';
import { formatDate } from '../utils/utils';
import SortableBarGraph from './SortableBarGraph';
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
@@ -64,7 +64,7 @@ const ShortUrlVisits = (
if (loading) {
const message = loadingLarge ? 'This is going to take a while... :S' : 'Loading...';
- return {message};
+ return {message};
}
if (error) {
@@ -76,7 +76,7 @@ const ShortUrlVisits = (
}
if (isEmpty(visits)) {
- return There are no visits matching current filter :(;
+ return There are no visits matching current filter :(;
}
const { os, browsers, referrers, countries, cities, citiesForMap } = processStatsFromVisits(
diff --git a/test/common/Home.test.js b/test/common/Home.test.js
index 249b3e5d..c5812e4f 100644
--- a/test/common/Home.test.js
+++ b/test/common/Home.test.js
@@ -1,12 +1,11 @@
import { shallow } from 'enzyme';
-import { values } from 'ramda';
import React from 'react';
import Home from '../../src/common/Home';
describe('', () => {
let wrapped;
const defaultProps = {
- resetSelectedServer: () => '',
+ resetSelectedServer: jest.fn(),
servers: { loading: false, list: {} },
};
const createComponent = (props) => {
@@ -17,12 +16,7 @@ describe('', () => {
return wrapped;
};
- afterEach(() => {
- if (wrapped !== undefined) {
- wrapped.unmount();
- wrapped = undefined;
- }
- });
+ afterEach(() => wrapped && wrapped.unmount());
it('resets selected server when mounted', () => {
const resetSelectedServer = jest.fn();
@@ -36,7 +30,6 @@ describe('', () => {
const wrapped = createComponent();
expect(wrapped.find('Link')).toHaveLength(1);
- expect(wrapped.find('ListGroup')).toHaveLength(0);
});
it('shows message when loading servers', () => {
@@ -45,21 +38,5 @@ describe('', () => {
expect(span).toHaveLength(1);
expect(span.text()).toContain('Trying to load servers...');
- expect(wrapped.find('ListGroup')).toHaveLength(0);
- });
-
- it('shows servers list when list of servers is not empty', () => {
- const servers = {
- loading: false,
- list: {
- 1: { name: 'foo', id: '123' },
- 2: { name: 'bar', id: '456' },
- },
- };
- const wrapped = createComponent({ servers });
-
- expect(wrapped.find('Link')).toHaveLength(0);
- expect(wrapped.find('ListGroup')).toHaveLength(1);
- expect(wrapped.find('ListGroupItem')).toHaveLength(values(servers).length);
});
});
diff --git a/test/servers/ServersListGroup.test.js b/test/servers/ServersListGroup.test.js
new file mode 100644
index 00000000..b2871bbe
--- /dev/null
+++ b/test/servers/ServersListGroup.test.js
@@ -0,0 +1,34 @@
+import { shallow } from 'enzyme';
+import React from 'react';
+import { ListGroup } from 'reactstrap';
+import ServersListGroup from '../../src/servers/ServersListGroup';
+
+describe('', () => {
+ let wrapped;
+ const createComponent = (servers) => {
+ wrapped = shallow(The list of servers);
+
+ return wrapped;
+ };
+
+ afterEach(() => wrapped && wrapped.unmount());
+
+ it('Renders title', () => {
+ const wrapped = createComponent([]);
+ const title = wrapped.find('h5');
+
+ expect(title).toHaveLength(1);
+ expect(title.text()).toEqual('The list of servers');
+ });
+
+ it('shows servers list', () => {
+ const servers = [
+ { name: 'foo', id: '123' },
+ { name: 'bar', id: '456' },
+ ];
+ const wrapped = createComponent(servers);
+
+ expect(wrapped.find(ListGroup)).toHaveLength(1);
+ expect(wrapped.find('ServerListItem')).toHaveLength(servers.length);
+ });
+});
diff --git a/test/tags/TagsList.test.js b/test/tags/TagsList.test.js
index 5a2ce549..bba25539 100644
--- a/test/tags/TagsList.test.js
+++ b/test/tags/TagsList.test.js
@@ -2,7 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { identity } from 'ramda';
import createTagsList from '../../src/tags/TagsList';
-import MutedMessage from '../../src/utils/MutedMessage';
+import Message from '../../src/utils/Message';
import SearchField from '../../src/utils/SearchField';
import { rangeOf } from '../../src/utils/utils';
@@ -28,7 +28,7 @@ describe('', () => {
it('shows a loading message when tags are being loaded', () => {
const wrapper = createWrapper({ loading: true });
- const loadingMsg = wrapper.find(MutedMessage);
+ const loadingMsg = wrapper.find(Message);
expect(loadingMsg).toHaveLength(1);
expect(loadingMsg.html()).toContain('Loading...');
@@ -44,7 +44,7 @@ describe('', () => {
it('shows a message when the list of tags is empty', () => {
const wrapper = createWrapper({ filteredTags: [] });
- const msg = wrapper.find(MutedMessage);
+ const msg = wrapper.find(Message);
expect(msg).toHaveLength(1);
expect(msg.html()).toContain('No tags found');
diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.js
index 004019cf..785f216a 100644
--- a/test/visits/ShortUrlVisits.test.js
+++ b/test/visits/ShortUrlVisits.test.js
@@ -3,7 +3,7 @@ import { shallow } from 'enzyme';
import { identity } from 'ramda';
import { Card } from 'reactstrap';
import createShortUrlVisits from '../../src/visits/ShortUrlVisits';
-import MutedMessage from '../../src/utils/MutedMessage';
+import Message from '../../src/utils/Message';
import GraphCard from '../../src/visits/GraphCard';
import SortableBarGraph from '../../src/visits/SortableBarGraph';
import DateRangeRow from '../../src/utils/DateRangeRow';
@@ -44,7 +44,7 @@ describe('', () => {
it('renders a preloader when visits are loading', () => {
const wrapper = createComponent({ loading: true });
- const loadingMessage = wrapper.find(MutedMessage);
+ const loadingMessage = wrapper.find(Message);
expect(loadingMessage).toHaveLength(1);
expect(loadingMessage.html()).toContain('Loading...');
@@ -52,7 +52,7 @@ describe('', () => {
it('renders a warning when loading large amounts of visits', () => {
const wrapper = createComponent({ loading: true, loadingLarge: true });
- const loadingMessage = wrapper.find(MutedMessage);
+ const loadingMessage = wrapper.find(Message);
expect(loadingMessage).toHaveLength(1);
expect(loadingMessage.html()).toContain('This is going to take a while... :S');
@@ -68,7 +68,7 @@ describe('', () => {
it('renders a message when visits are loaded but the list is empty', () => {
const wrapper = createComponent({ loading: false, error: false, visits: [] });
- const message = wrapper.find(MutedMessage);
+ const message = wrapper.find(Message);
expect(message).toHaveLength(1);
expect(message.html()).toContain('There are no visits matching current filter :(');