diff --git a/package.json b/package.json
index 91bdad98..df7f336f 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"fs-extra": "3.0.1",
"html-webpack-plugin": "2.29.0",
"jest": "20.0.4",
+ "moment": "^2.22.2",
"object-assign": "4.1.1",
"postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8",
@@ -43,6 +44,7 @@
"react": "^16.3.2",
"react-dev-utils": "^5.0.1",
"react-dom": "^16.3.2",
+ "react-moment": "^0.7.6",
"react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
"reactstrap": "^6.0.1",
diff --git a/src/index.scss b/src/index.scss
index e6afb95a..ae72fb45 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -1,3 +1,7 @@
* {
outline: none !important;
}
+
+.nowrap {
+ white-space: nowrap;
+}
diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js
index 8b637049..7bb4e10f 100644
--- a/src/servers/reducers/selectedServer.js
+++ b/src/servers/reducers/selectedServer.js
@@ -1,18 +1,10 @@
-import ServersService from '../services';
-import { LOAD_SERVER } from '../../reducers/types';
+import { LIST_SHORT_URLS } from '../../reducers/types';
export default function selectedServerReducer(state = null, action) {
switch (action.type) {
- case LOAD_SERVER:
+ case LIST_SHORT_URLS:
return action.selectedServer;
default:
return state;
}
}
-
-export const loadServer = serverId => {
- return {
- type: LOAD_SERVER,
- selectedServer: ServersService.findServerById(serverId),
- };
-};
diff --git a/src/servers/services/index.js b/src/servers/services/index.js
index bfbae11e..60dd30e0 100644
--- a/src/servers/services/index.js
+++ b/src/servers/services/index.js
@@ -1,5 +1,4 @@
-const PREFIX = 'shlink';
-const buildPath = path => `${PREFIX}.${path}`;
+import Storage from '../../utils/Storage';
export class ServersService {
constructor(storage) {
@@ -7,7 +6,7 @@ export class ServersService {
}
listServers = () => {
- return JSON.parse(this.storage.getItem(buildPath('servers')) || '{}');
+ return this.storage.get('servers');
};
findServerById = serverId => {
@@ -16,4 +15,4 @@ export class ServersService {
}
}
-export default new ServersService(localStorage);
+export default new ServersService(Storage);
diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js
index e4907a67..8f3e7cd9 100644
--- a/src/short-urls/ShortUrlsList.js
+++ b/src/short-urls/ShortUrlsList.js
@@ -1,34 +1,70 @@
import React from 'react';
+import Moment from 'react-moment';
import { connect } from 'react-redux';
+import Tag from '../utils/Tag';
import { listShortUrls } from './reducers/shortUrlsList';
import { isEmpty } from 'ramda';
export class ShortUrlsList extends React.Component {
componentDidMount() {
const { match } = this.props;
- this.props.listShortUrls(match.params.serverId);
+ this.props.listShortUrls(match.params.serverId, { page: match.params.page });
}
render() {
return (
-
- {this.renderShortUrls()}
-
+
+
+
+ | Created at |
+ Short URL |
+ Original URL |
+ Tags |
+ Visits |
+ - |
+
+
+
+ {this.renderShortUrls()}
+
+
);
}
renderShortUrls() {
- const { shortUrlsList } = this.props;
+ const { shortUrlsList, selectedServer } = this.props;
if (isEmpty(shortUrlsList)) {
return Loading...;
}
return shortUrlsList.map(shortUrl => (
- {`${shortUrl.shortCode}`}
+
+ | {shortUrl.dateCreated} |
+
+
+ {`${selectedServer.url}/${shortUrl.shortCode}`}
+
+ |
+
+ {shortUrl.originalUrl}
+ |
+ {ShortUrlsList.renderTags(shortUrl.tags)} |
+ {shortUrl.visitsCount} |
+ |
+
));
}
+
+ static renderTags(tags) {
+ if (isEmpty(tags)) {
+ return No tags;
+ }
+
+ return tags.map(tag => );
+ }
}
export default connect(state => ({
- shortUrlsList: state.shortUrlsList
+ shortUrlsList: state.shortUrlsList,
+ selectedServer: state.selectedServer,
}), { listShortUrls })(ShortUrlsList);
diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js
index f739b0b6..4e218192 100644
--- a/src/short-urls/reducers/shortUrlsList.js
+++ b/src/short-urls/reducers/shortUrlsList.js
@@ -11,12 +11,12 @@ export default function shortUrlsListReducer(state = [], action) {
}
}
-export const listShortUrls = (serverId) => {
+export const listShortUrls = (serverId, params = {}) => {
return async dispatch => {
const selectedServer = ServersService.findServerById(serverId);
ShlinkApiClient.setConfig(selectedServer);
- const shortUrls = await ShlinkApiClient.listShortUrls();
- dispatch({ type: LIST_SHORT_URLS, shortUrls });
+ const shortUrls = await ShlinkApiClient.listShortUrls(params);
+ dispatch({ type: LIST_SHORT_URLS, shortUrls, selectedServer });
};
};
diff --git a/src/utils/ColorGenerator.js b/src/utils/ColorGenerator.js
new file mode 100644
index 00000000..30e041a8
--- /dev/null
+++ b/src/utils/ColorGenerator.js
@@ -0,0 +1,32 @@
+import Storage from './Storage';
+
+const buildRandomColor = () => {
+ const letters = '0123456789ABCDEF';
+ let color = '#';
+ for (let i = 0; i < 6; i++ ) {
+ color += letters[Math.floor(Math.random() * 16)];
+ }
+ return color;
+};
+
+export class ColorGenerator {
+ constructor(storage) {
+ this.storage = storage;
+ this.colors = this.storage.get('colors') || {};
+ }
+
+ getColorForKey = key => {
+ let color = this.colors[key];
+ if (color) {
+ return color;
+ }
+
+ // If a color has not been set yet, generate a random one and save it
+ color = buildRandomColor();
+ this.colors[key] = color;
+ this.storage.set('colors', this.colors);
+ return color;
+ };
+}
+
+export default new ColorGenerator(Storage);
diff --git a/src/utils/Storage.js b/src/utils/Storage.js
new file mode 100644
index 00000000..ae58110a
--- /dev/null
+++ b/src/utils/Storage.js
@@ -0,0 +1,17 @@
+const PREFIX = 'shlink';
+const buildPath = path => `${PREFIX}.${path}`;
+
+export class Storage {
+ constructor(localStorage) {
+ this.localStorage = localStorage;
+ }
+
+ get = key => {
+ const item = this.localStorage.getItem(buildPath(key));
+ return item ? JSON.parse(item) : undefined;
+ };
+
+ set = (key, value) => this.localStorage.setItem(buildPath(key), JSON.stringify(value));
+}
+
+export default new Storage(localStorage);
diff --git a/src/utils/Tag.js b/src/utils/Tag.js
new file mode 100644
index 00000000..4e483e1b
--- /dev/null
+++ b/src/utils/Tag.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import ColorGenerator from '../utils/ColorGenerator';
+import './Tag.scss';
+
+export class Tag extends React.Component {
+ constructor(props) {
+ super(props);
+ this.colorGenerator = props.ColorGenerator;
+ }
+
+ render() {
+ return (
+
+ {this.props.text}
+
+ );
+ }
+}
+
+export default connect(state => ({ ColorGenerator }))(Tag);
diff --git a/src/utils/Tag.scss b/src/utils/Tag.scss
new file mode 100644
index 00000000..2c7a1f73
--- /dev/null
+++ b/src/utils/Tag.scss
@@ -0,0 +1,7 @@
+.tag {
+ color: #fff;
+}
+
+.tag:not(:last-child) {
+ margin-right: 3px;
+}