mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 23:07:26 +03:00
Defined visit stats page
This commit is contained in:
parent
c02b0e0591
commit
a75c7309f7
8 changed files with 87 additions and 31 deletions
|
@ -1,6 +1,6 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { isEmpty, isNil, pipe, reject } from 'ramda';
|
import { isEmpty, isNil, reject } from 'ramda';
|
||||||
|
|
||||||
const API_VERSION = '1';
|
const API_VERSION = '1';
|
||||||
|
|
||||||
|
@ -22,11 +22,6 @@ export class ShlinkApiClient {
|
||||||
this._apiKey = apiKey;
|
this._apiKey = apiKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of short URLs
|
|
||||||
* @param options
|
|
||||||
* @returns {Promise<Array>}
|
|
||||||
*/
|
|
||||||
listShortUrls = (options = {}) =>
|
listShortUrls = (options = {}) =>
|
||||||
this._performRequest('/short-codes', 'GET', options)
|
this._performRequest('/short-codes', 'GET', options)
|
||||||
.then(resp => resp.data.shortUrls)
|
.then(resp => resp.data.shortUrls)
|
||||||
|
@ -39,6 +34,11 @@ export class ShlinkApiClient {
|
||||||
.catch(e => this._handleAuthError(e, this.listShortUrls, [filteredOptions]));
|
.catch(e => this._handleAuthError(e, this.listShortUrls, [filteredOptions]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getShortUrlVisits = shortCode =>
|
||||||
|
this._performRequest(`/short-codes/${shortCode}/visits`, 'GET')
|
||||||
|
.then(resp => resp.data.visits.data)
|
||||||
|
.catch(e => this._handleAuthError(e, this.listShortUrls, [shortCode]));
|
||||||
|
|
||||||
_performRequest = async (url, method = 'GET', params = {}, data = {}) => {
|
_performRequest = async (url, method = 'GET', params = {}, data = {}) => {
|
||||||
if (isEmpty(this._token)) {
|
if (isEmpty(this._token)) {
|
||||||
this._token = await this._authenticate();
|
this._token = await this._authenticate();
|
||||||
|
|
|
@ -9,7 +9,8 @@ import AsideMenu from './AsideMenu';
|
||||||
import { pick } from 'ramda';
|
import { pick } from 'ramda';
|
||||||
|
|
||||||
export class MenuLayout extends React.Component {
|
export class MenuLayout extends React.Component {
|
||||||
componentDidMount() {
|
// FIXME Shouldn't use componentWillMount, but this code has to be run before children components are rendered
|
||||||
|
componentWillMount() {
|
||||||
const { serverId } = this.props.match.params;
|
const { serverId } = this.props.match.params;
|
||||||
this.props.selectServer(serverId);
|
this.props.selectServer(serverId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import searchIcon from '@fortawesome/fontawesome-free-solid/faSearch';
|
||||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { updateShortUrlsList } from './reducers/shortUrlsList';
|
import { listShortUrls } from './reducers/shortUrlsList';
|
||||||
import './SearchBar.scss';
|
import './SearchBar.scss';
|
||||||
import { pick } from 'ramda';
|
import { pick } from 'ramda';
|
||||||
|
|
||||||
|
@ -47,10 +47,10 @@ export class SearchBar extends React.Component {
|
||||||
resetTimer();
|
resetTimer();
|
||||||
|
|
||||||
this.timer = setTimeout(() => {
|
this.timer = setTimeout(() => {
|
||||||
this.props.updateShortUrlsList({ ...this.props.shortUrlsListParams, searchTerm });
|
this.props.listShortUrls({ ...this.props.shortUrlsListParams, searchTerm });
|
||||||
resetTimer();
|
resetTimer();
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(pick(['shortUrlsListParams']), { updateShortUrlsList })(SearchBar);
|
export default connect(pick(['shortUrlsListParams']), { listShortUrls })(SearchBar);
|
||||||
|
|
|
@ -1,11 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { pick } from 'ramda';
|
||||||
|
import { getShortUrlVisits } from './reducers/shortUrlVisits';
|
||||||
|
|
||||||
export class ShortUrlsVisits extends React.Component {
|
export class ShortUrlsVisits extends React.Component {
|
||||||
render() {
|
componentDidMount() {
|
||||||
const { match: { params } } = this.props;
|
const { match: { params } } = this.props;
|
||||||
return <div>Visits for <b>{params.shortCode}</b></div>;
|
this.props.getShortUrlVisits(params.shortCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { match: { params }, selectedServer } = this.props;
|
||||||
|
const serverUrl = selectedServer ? selectedServer.url : '';
|
||||||
|
const shortUrl = `${serverUrl}/${params.shortCode}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="short-urls-container">
|
||||||
|
<div className="card bg-light">
|
||||||
|
<div className="card-body">
|
||||||
|
<h2>Visit stats for <a target="_blank" href={shortUrl}>{shortUrl}</a></h2>
|
||||||
|
{/* TODO Once Shlink's API allows it, add total visits counter, long URL and creation time */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect()(ShortUrlsVisits);
|
export default connect(pick(['selectedServer']), {
|
||||||
|
getShortUrlVisits
|
||||||
|
})(ShortUrlsVisits);
|
||||||
|
|
|
@ -12,8 +12,8 @@ import { pick } from 'ramda';
|
||||||
|
|
||||||
export class ShortUrlsList extends React.Component {
|
export class ShortUrlsList extends React.Component {
|
||||||
refreshList = extraParams => {
|
refreshList = extraParams => {
|
||||||
const { listShortUrls, shortUrlsListParams, match: { params } } = this.props;
|
const { listShortUrls, shortUrlsListParams } = this.props;
|
||||||
listShortUrls(params.serverId, {
|
listShortUrls({
|
||||||
...shortUrlsListParams,
|
...shortUrlsListParams,
|
||||||
...extraParams
|
...extraParams
|
||||||
});
|
});
|
||||||
|
|
46
src/short-urls/reducers/shortUrlVisits.js
Normal file
46
src/short-urls/reducers/shortUrlVisits.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||||
|
|
||||||
|
const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
|
||||||
|
const GET_SHORT_URL_VISITS_ERROR = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_ERROR';
|
||||||
|
const GET_SHORT_URL_VISITS = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
visits: [],
|
||||||
|
loading: false,
|
||||||
|
error: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function dispatch (state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case GET_SHORT_URL_VISITS_START:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true
|
||||||
|
};
|
||||||
|
case GET_SHORT_URL_VISITS_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
error: true
|
||||||
|
};
|
||||||
|
case GET_SHORT_URL_VISITS:
|
||||||
|
return {
|
||||||
|
visits: action.visits,
|
||||||
|
loading: false,
|
||||||
|
error: false
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getShortUrlVisits = shortCode => async dispatch => {
|
||||||
|
dispatch({ type: GET_SHORT_URL_VISITS_START });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const visits = await ShlinkApiClient.getShortUrlVisits(shortCode);
|
||||||
|
dispatch({ visits, type: GET_SHORT_URL_VISITS });
|
||||||
|
} catch (e) {
|
||||||
|
dispatch({ type: GET_SHORT_URL_VISITS_ERROR });
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,10 +1,8 @@
|
||||||
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||||
import ServersService from '../../servers/services/ServersService';
|
|
||||||
|
|
||||||
export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';
|
const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';
|
||||||
export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR';
|
const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR';
|
||||||
export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
|
export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
|
||||||
export const UPDATE_SHORT_URLS_LIST = LIST_SHORT_URLS;
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
shortUrls: [],
|
shortUrls: [],
|
||||||
|
@ -32,16 +30,7 @@ export default function reducer(state = initialState, action) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listShortUrls = (serverId, params = {}) => {
|
export const listShortUrls = (params = {}) => async dispatch => {
|
||||||
// FIXME There should be a way to not need this, however, the active server is set when any route is loaded, in an
|
|
||||||
// FIXME outer component's componentDidMount, which makes it be invoked after this action
|
|
||||||
const selectedServer = ServersService.findServerById(serverId);
|
|
||||||
ShlinkApiClient.setConfig(selectedServer);
|
|
||||||
|
|
||||||
return updateShortUrlsList(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateShortUrlsList = (params = {}) => async dispatch => {
|
|
||||||
dispatch({ type: LIST_SHORT_URLS_START });
|
dispatch({ type: LIST_SHORT_URLS_START });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { UPDATE_SHORT_URLS_LIST, LIST_SHORT_URLS } from './shortUrlsList';
|
import { LIST_SHORT_URLS } from './shortUrlsList';
|
||||||
|
|
||||||
export default function reducer(state = { page: 1 }, action) {
|
export default function reducer(state = { page: 1 }, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case UPDATE_SHORT_URLS_LIST:
|
|
||||||
case LIST_SHORT_URLS:
|
case LIST_SHORT_URLS:
|
||||||
return { ...state, ...action.params };
|
return { ...state, ...action.params };
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in a new issue