From 49290b56ee9debef2065a73b72f36fdebb737da5 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 16 Aug 2018 18:59:00 +0200 Subject: [PATCH] Created tags list page --- src/api/ShlinkApiClient.js | 5 ++++ src/common/AsideMenu.js | 15 ++++++++-- src/common/MenuLayout.js | 6 ++++ src/index.scss | 2 +- src/reducers/index.js | 2 ++ src/short-urls/CreateShortUrl.js | 2 +- src/short-urls/ShortUrlVisits.js | 3 +- src/short-urls/ShortUrls.js | 2 +- src/tags/TagsList.js | 51 ++++++++++++++++++++++++++++++++ src/tags/TagsList.scss | 18 +++++++++++ src/tags/reducers/tagsList.js | 49 ++++++++++++++++++++++++++++++ src/utils/Tag.js | 3 +- 12 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 src/tags/TagsList.js create mode 100644 src/tags/TagsList.scss create mode 100644 src/tags/reducers/tagsList.js diff --git a/src/api/ShlinkApiClient.js b/src/api/ShlinkApiClient.js index 7714dd3b..7dac876e 100644 --- a/src/api/ShlinkApiClient.js +++ b/src/api/ShlinkApiClient.js @@ -49,6 +49,11 @@ export class ShlinkApiClient { .then(resp => resp.data.tags) .catch(e => this._handleAuthError(e, this.updateShortUrlTags, [shortCode, tags])); + listTags = () => + this._performRequest('/tags', 'GET') + .then(resp => resp.data.tags.data) + .catch(e => this._handleAuthError(e, this.listTags, [])); + _performRequest = async (url, method = 'GET', params = {}, data = {}) => { if (isEmpty(this._token)) { this._token = await this._authenticate(); diff --git a/src/common/AsideMenu.js b/src/common/AsideMenu.js index e32fbefe..df22f91d 100644 --- a/src/common/AsideMenu.js +++ b/src/common/AsideMenu.js @@ -1,5 +1,6 @@ -import listIcon from '@fortawesome/fontawesome-free-solid/faBars'; -import createIcon from '@fortawesome/fontawesome-free-solid/faPlus'; +import listIcon from '@fortawesome/fontawesome-free-solid/faList'; +import createIcon from '@fortawesome/fontawesome-free-solid/faLink'; +import tagsIcon from '@fortawesome/fontawesome-free-solid/faTags'; import FontAwesomeIcon from '@fortawesome/react-fontawesome'; import React from 'react'; import { NavLink } from 'react-router-dom'; @@ -41,9 +42,17 @@ export default function AsideMenu({ selectedServer, className, showOnMobile }) { activeClassName="aside-menu__item--selected" to={`/server/${serverId}/create-short-url`} > - + Create short URL + + + List tags + + diff --git a/src/index.scss b/src/index.scss index 9e7eb41f..2e52c766 100644 --- a/src/index.scss +++ b/src/index.scss @@ -25,7 +25,7 @@ body, @extend .bg-main; } -.short-urls-container { +.shlink-container { padding: 20px 0; @media (min-width: $mdMin) { diff --git a/src/reducers/index.js b/src/reducers/index.js index 20c0c481..96c904b1 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -7,6 +7,7 @@ import shortUrlsListParamsReducer from '../short-urls/reducers/shortUrlsListPara import shortUrlCreationResultReducer from '../short-urls/reducers/shortUrlCreationResult'; import shortUrlVisitsReducer from '../short-urls/reducers/shortUrlVisits'; import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags'; +import tagsListReducer from '../tags/reducers/tagsList'; export default combineReducers({ servers: serversReducer, @@ -16,4 +17,5 @@ export default combineReducers({ shortUrlCreationResult: shortUrlCreationResultReducer, shortUrlVisits: shortUrlVisitsReducer, shortUrlTags: shortUrlTagsReducer, + tagsList: tagsListReducer, }); diff --git a/src/short-urls/CreateShortUrl.js b/src/short-urls/CreateShortUrl.js index 2a5abae1..ca82e29a 100644 --- a/src/short-urls/CreateShortUrl.js +++ b/src/short-urls/CreateShortUrl.js @@ -53,7 +53,7 @@ export class CreateShortUrl extends React.Component { }; return ( -
+
{shortUrl.dateCreated} ; + return ( -
+
diff --git a/src/short-urls/ShortUrls.js b/src/short-urls/ShortUrls.js index 36f9537d..4d421dd8 100644 --- a/src/short-urls/ShortUrls.js +++ b/src/short-urls/ShortUrls.js @@ -11,7 +11,7 @@ export function ShortUrls(props) { const urlsListKey = `${params.serverId}_${params.page}`; return ( -
+
diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js new file mode 100644 index 00000000..f93f5361 --- /dev/null +++ b/src/tags/TagsList.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { pick, splitEvery } from 'ramda'; +import { listTags } from './reducers/tagsList'; +import './TagsList.scss'; +import { Card } from 'reactstrap'; +import ColorGenerator from '../utils/ColorGenerator'; + +export class TagsList extends React.Component { + componentDidMount() { + const { listTags } = this.props; + listTags(); + } + + render() { + const { tagsList, colorGenerator } = this.props; + const tagsCount = Math.round(tagsList.tags.length); + if (tagsCount < 1) { + return
No tags
; + } + + const tagsGroups = splitEvery(Math.round(tagsCount / 4), tagsList.tags); + + return ( +
+
+ {tagsGroups.map((group, index) => ( +
+ {group.map(tag => ( +
+ +
{tag}
+
+
+ ))} +
+ ))} +
+
+ ); + } +} + +TagsList.defaultProps = { + colorGenerator: ColorGenerator +}; + +export default connect(pick(['tagsList']), { listTags })(TagsList); diff --git a/src/tags/TagsList.scss b/src/tags/TagsList.scss new file mode 100644 index 00000000..7765c0d7 --- /dev/null +++ b/src/tags/TagsList.scss @@ -0,0 +1,18 @@ +.tags-list__tag-container { + border-radius: .26rem; +} + +.tags-list__tag-card.tags-list__tag-card { + padding: .75rem 2.5rem 0.75rem 1rem; + background-color: #eeeeee; + margin-bottom: .5rem; + transition: background-color 200ms, color 200ms; +} +.tags-list__tag-card.tags-list__tag-card:hover { + background-color: transparent; + color: #fff; +} + +.tags-list__tag-title { + margin: 0; +} diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js new file mode 100644 index 00000000..c5fe5ab0 --- /dev/null +++ b/src/tags/reducers/tagsList.js @@ -0,0 +1,49 @@ +import ShlinkApiClient from '../../api/ShlinkApiClient'; + +const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; +const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR'; +const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; + +const defaultState = { + tags: [], + loading: false, + error: false, +}; + +export default function reducer(state = defaultState, action) { + switch(action.type) { + case LIST_TAGS_START: + return { + ...state, + loading: true, + error: false, + }; + case LIST_TAGS_ERROR: + return { + ...state, + loading: false, + error: true, + }; + case LIST_TAGS: + return { + tags: action.tags, + loading: false, + error: false, + }; + default: + return state; + } +} + +export const _listTags = ShlinkApiClient => async dispatch => { + dispatch({ type: LIST_TAGS_START }); + + try { + const tags = await ShlinkApiClient.listTags(); + dispatch({ tags, type: LIST_TAGS }); + } catch (e) { + dispatch({ type: LIST_TAGS_ERROR }); + throw e; + } +}; +export const listTags = () => _listTags(ShlinkApiClient); diff --git a/src/utils/Tag.js b/src/utils/Tag.js index f6a49c44..eeadfbcb 100644 --- a/src/utils/Tag.js +++ b/src/utils/Tag.js @@ -6,6 +6,7 @@ export default function Tag ( { colorGenerator, text, + children, clearable, onClick = () => ({}), onClose = () => ({}) @@ -17,7 +18,7 @@ export default function Tag ( style={{ backgroundColor: colorGenerator.getColorForKey(text), cursor: clearable ? 'auto' : 'pointer' }} onClick={onClick} > - {text} + {children || text} {clearable && ×} );