mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 01:20:24 +03:00
Created tags list page
This commit is contained in:
parent
03113583f0
commit
49290b56ee
12 changed files with 150 additions and 8 deletions
|
@ -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();
|
||||
|
|
|
@ -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`}
|
||||
>
|
||||
<FontAwesomeIcon icon={createIcon} />
|
||||
<FontAwesomeIcon icon={createIcon} flip="horizontal" />
|
||||
<span className="aside-menu__item-text">Create short URL</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
className="aside-menu__item"
|
||||
activeClassName="aside-menu__item--selected"
|
||||
to={`/server/${serverId}/tags`}
|
||||
>
|
||||
<FontAwesomeIcon icon={tagsIcon} />
|
||||
<span className="aside-menu__item-text">List tags</span>
|
||||
</NavLink>
|
||||
|
||||
<DeleteServerButton
|
||||
className="aside-menu__item aside-menu__item--danger"
|
||||
|
|
|
@ -13,6 +13,7 @@ import burgerIcon from '@fortawesome/fontawesome-free-solid/faBars';
|
|||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||
import classnames from 'classnames';
|
||||
import './MenuLayout.scss';
|
||||
import TagsList from '../tags/TagsList';
|
||||
|
||||
export class MenuLayout extends React.Component {
|
||||
state = { showSideBar: false };
|
||||
|
@ -78,6 +79,11 @@ export class MenuLayout extends React.Component {
|
|||
path="/server/:serverId/short-code/:shortCode/visits"
|
||||
component={ShortUrlsVisits}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/tags"
|
||||
component={TagsList}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,7 +25,7 @@ body,
|
|||
@extend .bg-main;
|
||||
}
|
||||
|
||||
.short-urls-container {
|
||||
.shlink-container {
|
||||
padding: 20px 0;
|
||||
|
||||
@media (min-width: $mdMin) {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -53,7 +53,7 @@ export class CreateShortUrl extends React.Component {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="short-urls-container">
|
||||
<div className="shlink-container">
|
||||
<form onSubmit={save}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
|
|
|
@ -115,8 +115,9 @@ export class ShortUrlsVisits extends React.Component {
|
|||
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
||||
</UncontrolledTooltip>
|
||||
</span>;
|
||||
|
||||
return (
|
||||
<div className="short-urls-container">
|
||||
<div className="shlink-container">
|
||||
<header>
|
||||
<Card className="bg-light">
|
||||
<CardBody>
|
||||
|
|
|
@ -11,7 +11,7 @@ export function ShortUrls(props) {
|
|||
const urlsListKey = `${params.serverId}_${params.page}`;
|
||||
|
||||
return (
|
||||
<div className="short-urls-container">
|
||||
<div className="shlink-container">
|
||||
<div className="form-group"><SearchBar /></div>
|
||||
<ShortUrlsList {...props} shortUrlsList={props.shortUrlsList.data || []} key={urlsListKey} />
|
||||
<Paginator paginator={props.shortUrlsList.pagination} serverId={props.match.params.serverId} />
|
||||
|
|
51
src/tags/TagsList.js
Normal file
51
src/tags/TagsList.js
Normal file
|
@ -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 <div>No tags</div>;
|
||||
}
|
||||
|
||||
const tagsGroups = splitEvery(Math.round(tagsCount / 4), tagsList.tags);
|
||||
|
||||
return (
|
||||
<div className="shlink-container">
|
||||
<div className="row">
|
||||
{tagsGroups.map((group, index) => (
|
||||
<div key={index} className="col-md-6 col-xl-3">
|
||||
{group.map(tag => (
|
||||
<div
|
||||
style={{ backgroundColor: colorGenerator.getColorForKey(tag) }}
|
||||
className="tags-list__tag-container"
|
||||
>
|
||||
<Card body className="tags-list__tag-card">
|
||||
<h5 className="tags-list__tag-title">{tag}</h5>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TagsList.defaultProps = {
|
||||
colorGenerator: ColorGenerator
|
||||
};
|
||||
|
||||
export default connect(pick(['tagsList']), { listTags })(TagsList);
|
18
src/tags/TagsList.scss
Normal file
18
src/tags/TagsList.scss
Normal file
|
@ -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;
|
||||
}
|
49
src/tags/reducers/tagsList.js
Normal file
49
src/tags/reducers/tagsList.js
Normal file
|
@ -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);
|
|
@ -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 && <span className="close tag__close-selected-tag" onClick={onClose}>×</span>}
|
||||
</span>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue