mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-13 19:13:46 +00:00
Updated styles in javascript to fulfill adidas rules
This commit is contained in:
@@ -6,11 +6,11 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Collapse } from 'reactstrap';
|
||||
import DateInput from '../common/DateInput';
|
||||
import TagsSelector from '../utils/TagsSelector';
|
||||
import CreateShortUrlResult from './helpers/CreateShortUrlResult';
|
||||
import { createShortUrl, resetCreateShortUrl } from './reducers/shortUrlCreationResult';
|
||||
import TagsSelector from '../utils/TagsSelector';
|
||||
|
||||
export class CreateShortUrl extends React.Component {
|
||||
export class CreateShortUrlComponent extends React.Component {
|
||||
state = {
|
||||
longUrl: '',
|
||||
tags: [],
|
||||
@@ -18,35 +18,37 @@ export class CreateShortUrl extends React.Component {
|
||||
validSince: undefined,
|
||||
validUntil: undefined,
|
||||
maxVisits: undefined,
|
||||
moreOptionsVisible: false
|
||||
moreOptionsVisible: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { createShortUrl, shortUrlCreationResult, resetCreateShortUrl } = this.props;
|
||||
|
||||
const changeTags = tags => this.setState({ tags: tags.map(pipe(trim, replace(/ /g, '-'))) });
|
||||
const renderOptionalInput = (id, placeholder, type = 'text', props = {}) =>
|
||||
const changeTags = (tags) => this.setState({ tags: tags.map(pipe(trim, replace(/ /g, '-'))) });
|
||||
const renderOptionalInput = (id, placeholder, type = 'text', props = {}) => (
|
||||
<input
|
||||
className="form-control"
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={this.state[id]}
|
||||
onChange={e => this.setState({ [id]: e.target.value })}
|
||||
onChange={(e) => this.setState({ [id]: e.target.value })}
|
||||
{...props}
|
||||
/>;
|
||||
const createDateInput = (id, placeholder, props = {}) =>
|
||||
/>
|
||||
);
|
||||
const createDateInput = (id, placeholder, props = {}) => (
|
||||
<DateInput
|
||||
selected={this.state[id]}
|
||||
placeholderText={placeholder}
|
||||
onChange={date => this.setState({ [id]: date })}
|
||||
isClearable
|
||||
onChange={(date) => this.setState({ [id]: date })}
|
||||
{...props}
|
||||
/>;
|
||||
const formatDate = date => isNil(date) ? date : date.format();
|
||||
const save = e => {
|
||||
/>
|
||||
);
|
||||
const formatDate = (date) => isNil(date) ? date : date.format();
|
||||
const save = (e) => {
|
||||
e.preventDefault();
|
||||
createShortUrl(pipe(
|
||||
dissoc('moreOptionsVisible'), // Remove moreOptionsVisible property
|
||||
dissoc('moreOptionsVisible'),
|
||||
assoc('validSince', formatDate(this.state.validSince)),
|
||||
assoc('validUntil', formatDate(this.state.validUntil))
|
||||
)(this.state));
|
||||
@@ -62,7 +64,7 @@ export class CreateShortUrl extends React.Component {
|
||||
placeholder="Insert the URL to be shortened"
|
||||
required
|
||||
value={this.state.longUrl}
|
||||
onChange={e => this.setState({ longUrl: e.target.value })}
|
||||
onChange={(e) => this.setState({ longUrl: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +97,7 @@ export class CreateShortUrl extends React.Component {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-secondary create-short-url__btn"
|
||||
onClick={() => this.setState({ moreOptionsVisible: !this.state.moreOptionsVisible })}
|
||||
onClick={() => this.setState(({ moreOptionsVisible }) => ({ moreOptionsVisible: !moreOptionsVisible }))}
|
||||
>
|
||||
<FontAwesomeIcon icon={this.state.moreOptionsVisible ? upIcon : downIcon} />
|
||||
|
||||
@@ -116,7 +118,9 @@ export class CreateShortUrl extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(pick(['shortUrlCreationResult']), {
|
||||
const CreateShortUrl = connect(pick([ 'shortUrlCreationResult' ]), {
|
||||
createShortUrl,
|
||||
resetCreateShortUrl
|
||||
})(CreateShortUrl);
|
||||
resetCreateShortUrl,
|
||||
})(CreateShortUrlComponent);
|
||||
|
||||
export default CreateShortUrl;
|
||||
|
||||
@@ -1,52 +1,61 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class Paginator extends React.Component {
|
||||
render() {
|
||||
const { paginator = {}, serverId } = this.props;
|
||||
const { currentPage, pagesCount = 0 } = paginator;
|
||||
if (pagesCount <= 1) {
|
||||
return null;
|
||||
const propTypes = {
|
||||
serverId: PropTypes.string.isRequired,
|
||||
paginator: PropTypes.shape({
|
||||
currentPage: PropTypes.number,
|
||||
pagesCount: PropTypes.number,
|
||||
}),
|
||||
};
|
||||
|
||||
export default function Paginator({ paginator = {}, serverId }) {
|
||||
const { currentPage, pagesCount = 0 } = paginator;
|
||||
|
||||
if (pagesCount <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderPages = () => {
|
||||
const pages = [];
|
||||
|
||||
for (let i = 1; i <= pagesCount; i++) {
|
||||
pages.push(
|
||||
<PaginationItem key={i} active={currentPage === i}>
|
||||
<PaginationLink
|
||||
tag={Link}
|
||||
to={`/server/${serverId}/list-short-urls/${i}`}
|
||||
>
|
||||
{i}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
);
|
||||
}
|
||||
|
||||
const renderPages = () => {
|
||||
const pages = [];
|
||||
return pages;
|
||||
};
|
||||
|
||||
for (let i = 1; i <= pagesCount; i++) {
|
||||
pages.push(
|
||||
<PaginationItem key={i} active={currentPage === i}>
|
||||
<PaginationLink
|
||||
tag={Link}
|
||||
to={`/server/${serverId}/list-short-urls/${i}`}
|
||||
>
|
||||
{i}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
return (
|
||||
<Pagination listClassName="flex-wrap">
|
||||
<PaginationItem disabled={currentPage === 1}>
|
||||
<PaginationLink
|
||||
previous
|
||||
tag={Link}
|
||||
to={`/server/${serverId}/list-short-urls/${currentPage - 1}`}
|
||||
/>
|
||||
</PaginationItem>
|
||||
{renderPages()}
|
||||
<PaginationItem disabled={currentPage >= pagesCount}>
|
||||
<PaginationLink
|
||||
next
|
||||
tag={Link}
|
||||
to={`/server/${serverId}/list-short-urls/${currentPage + 1}`}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</Pagination>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Pagination listClassName="flex-wrap">
|
||||
<PaginationItem disabled={currentPage === 1}>
|
||||
<PaginationLink
|
||||
previous
|
||||
tag={Link}
|
||||
to={`/server/${serverId}/list-short-urls/${currentPage - 1}`}
|
||||
/>
|
||||
</PaginationItem>
|
||||
{renderPages()}
|
||||
<PaginationItem disabled={currentPage >= pagesCount}>
|
||||
<PaginationLink
|
||||
next
|
||||
tag={Link}
|
||||
to={`/server/${serverId}/list-short-urls/${currentPage + 1}`}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</Pagination>
|
||||
);
|
||||
}
|
||||
|
||||
Paginator.propTypes = propTypes;
|
||||
|
||||
@@ -2,26 +2,34 @@ import tagsIcon from '@fortawesome/fontawesome-free-solid/faTags';
|
||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Tag from '../utils/Tag';
|
||||
import { listShortUrls } from './reducers/shortUrlsList';
|
||||
import { isEmpty, pick } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import Tag from '../utils/Tag';
|
||||
import SearchField from '../utils/SearchField';
|
||||
import { listShortUrls } from './reducers/shortUrlsList';
|
||||
import './SearchBar.scss';
|
||||
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
|
||||
|
||||
export function SearchBar({ listShortUrls, shortUrlsListParams }) {
|
||||
const propTypes = {
|
||||
listShortUrls: PropTypes.func,
|
||||
shortUrlsListParams: shortUrlsListParamsType,
|
||||
};
|
||||
|
||||
export function SearchBarComponent({ listShortUrls, shortUrlsListParams }) {
|
||||
const selectedTags = shortUrlsListParams.tags || [];
|
||||
|
||||
return (
|
||||
<div className="serach-bar-container">
|
||||
<SearchField onChange={
|
||||
searchTerm => listShortUrls({ ...shortUrlsListParams, searchTerm })
|
||||
}/>
|
||||
(searchTerm) => listShortUrls({ ...shortUrlsListParams, searchTerm })
|
||||
}
|
||||
/>
|
||||
|
||||
{!isEmpty(selectedTags) && (
|
||||
<h4 className="search-bar__selected-tag mt-2">
|
||||
<FontAwesomeIcon icon={tagsIcon} className="search-bar__tags-icon"/>
|
||||
<FontAwesomeIcon icon={tagsIcon} className="search-bar__tags-icon" />
|
||||
|
||||
{selectedTags.map(tag => (
|
||||
{selectedTags.map((tag) => (
|
||||
<Tag
|
||||
key={tag}
|
||||
text={tag}
|
||||
@@ -29,7 +37,7 @@ export function SearchBar({ listShortUrls, shortUrlsListParams }) {
|
||||
onClose={() => listShortUrls(
|
||||
{
|
||||
...shortUrlsListParams,
|
||||
tags: selectedTags.filter(selectedTag => selectedTag !== tag)
|
||||
tags: selectedTags.filter((selectedTag) => selectedTag !== tag),
|
||||
}
|
||||
)}
|
||||
/>
|
||||
@@ -40,4 +48,8 @@ export function SearchBar({ listShortUrls, shortUrlsListParams }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(pick(['shortUrlsListParams']), { listShortUrls })(SearchBar);
|
||||
SearchBarComponent.propTypes = propTypes;
|
||||
|
||||
const SearchBar = connect(pick([ 'shortUrlsListParams' ]), { listShortUrls })(SearchBarComponent);
|
||||
|
||||
export default SearchBar;
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
import preloader from '@fortawesome/fontawesome-free-solid/faCircleNotch'
|
||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
|
||||
import { isEmpty, mapObjIndexed, pick } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Doughnut, HorizontalBar } from 'react-chartjs-2'
|
||||
import Moment from 'react-moment'
|
||||
import { connect } from 'react-redux'
|
||||
import { Card, CardBody, CardHeader, UncontrolledTooltip } from 'reactstrap'
|
||||
import DateInput from '../common/DateInput'
|
||||
import preloader from '@fortawesome/fontawesome-free-solid/faCircleNotch';
|
||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||
import { isEmpty, mapObjIndexed, pick } from 'ramda';
|
||||
import React from 'react';
|
||||
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
|
||||
import Moment from 'react-moment';
|
||||
import { connect } from 'react-redux';
|
||||
import { Card, CardBody, CardHeader, UncontrolledTooltip } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import DateInput from '../common/DateInput';
|
||||
import {
|
||||
processOsStats,
|
||||
processBrowserStats,
|
||||
processCountriesStats,
|
||||
processReferrersStats,
|
||||
} from '../visits/services/VisitsParser'
|
||||
import { getShortUrlVisits } from './reducers/shortUrlVisits'
|
||||
import './ShortUrlVisits.scss'
|
||||
} from '../visits/services/VisitsParser';
|
||||
import MutedMessage from '../utils/MuttedMessage';
|
||||
import ExternalLink from '../utils/ExternalLink';
|
||||
import { serverType } from '../servers/prop-types';
|
||||
import { getShortUrlVisits, shortUrlVisitsType } from './reducers/shortUrlVisits';
|
||||
import './ShortUrlVisits.scss';
|
||||
|
||||
const propTypes = {
|
||||
processOsStats: PropTypes.func,
|
||||
processBrowserStats: PropTypes.func,
|
||||
processCountriesStats: PropTypes.func,
|
||||
processReferrersStats: PropTypes.func,
|
||||
match: PropTypes.object,
|
||||
getShortUrlVisits: PropTypes.func,
|
||||
selectedServer: serverType,
|
||||
shortUrlVisits: shortUrlVisitsType,
|
||||
};
|
||||
const defaultProps = {
|
||||
processOsStats,
|
||||
processBrowserStats,
|
||||
@@ -24,14 +37,15 @@ const defaultProps = {
|
||||
processReferrersStats,
|
||||
};
|
||||
|
||||
export class ShortUrlsVisits extends React.Component {
|
||||
export class ShortUrlsVisitsComponent extends React.Component {
|
||||
state = { startDate: undefined, endDate: undefined };
|
||||
loadVisits = () => {
|
||||
const { match: { params } } = this.props;
|
||||
this.props.getShortUrlVisits(params.shortCode, mapObjIndexed(
|
||||
value => value && value.format ? value.format('YYYY-MM-DD') : value,
|
||||
const { match: { params }, getShortUrlVisits } = this.props;
|
||||
|
||||
getShortUrlVisits(params.shortCode, mapObjIndexed(
|
||||
(value) => value && value.format ? value.format('YYYY-MM-DD') : value,
|
||||
this.state
|
||||
))
|
||||
));
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -46,7 +60,7 @@ export class ShortUrlsVisits extends React.Component {
|
||||
processBrowserStats,
|
||||
processCountriesStats,
|
||||
processReferrersStats,
|
||||
shortUrlVisits: { visits, loading, error, shortUrl }
|
||||
shortUrlVisits: { visits, loading, error, shortUrl },
|
||||
} = this.props;
|
||||
const serverUrl = selectedServer ? selectedServer.url : '';
|
||||
const shortLink = `${serverUrl}/${params.shortCode}`;
|
||||
@@ -63,31 +77,42 @@ export class ShortUrlsVisits extends React.Component {
|
||||
'#46BFBD',
|
||||
'#FDB45C',
|
||||
'#949FB1',
|
||||
'#4D5360'
|
||||
'#4D5360',
|
||||
],
|
||||
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
||||
borderWidth: 2
|
||||
}
|
||||
]
|
||||
borderWidth: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
const renderGraphCard = (title, stats, isBarChart, label) =>
|
||||
const renderGraphCard = (title, stats, isBarChart, label) => (
|
||||
<div className="col-md-6">
|
||||
<Card className="mt-4">
|
||||
<CardHeader>{title}</CardHeader>
|
||||
<CardBody>
|
||||
{!isBarChart && <Doughnut data={generateGraphData(stats, label || title, isBarChart)} options={{
|
||||
legend: {
|
||||
position: 'right'
|
||||
}
|
||||
}} />}
|
||||
{isBarChart && <HorizontalBar data={generateGraphData(stats, label || title, isBarChart)} options={{
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}} />}
|
||||
{!isBarChart && (
|
||||
<Doughnut
|
||||
data={generateGraphData(stats, label || title, isBarChart)}
|
||||
options={{
|
||||
legend: {
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isBarChart && (
|
||||
<HorizontalBar
|
||||
data={generateGraphData(stats, label || title, isBarChart)}
|
||||
options={{
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
const renderContent = () => {
|
||||
if (loading) {
|
||||
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> Loading...</MutedMessage>;
|
||||
@@ -115,13 +140,14 @@ export class ShortUrlsVisits extends React.Component {
|
||||
);
|
||||
};
|
||||
|
||||
const renderCreated = () =>
|
||||
const renderCreated = () => (
|
||||
<span>
|
||||
<b id="created"><Moment fromNow>{shortUrl.dateCreated}</Moment></b>
|
||||
<UncontrolledTooltip placement="bottom" target="created">
|
||||
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
||||
</UncontrolledTooltip>
|
||||
</span>;
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="shlink-container">
|
||||
@@ -133,20 +159,22 @@ export class ShortUrlsVisits extends React.Component {
|
||||
shortUrl.visitsCount &&
|
||||
<span className="badge badge-main float-right">Visits: {shortUrl.visitsCount}</span>
|
||||
}
|
||||
Visit stats for <a target="_blank" href={shortLink}>{shortLink}</a>
|
||||
Visit stats for <ExternalLink href={shortLink}>{shortLink}</ExternalLink>
|
||||
</h2>
|
||||
<hr />
|
||||
{shortUrl.dateCreated && <div>
|
||||
Created:
|
||||
|
||||
{loading && <small>Loading...</small>}
|
||||
{!loading && renderCreated()}
|
||||
</div>}
|
||||
{shortUrl.dateCreated && (
|
||||
<div>
|
||||
Created:
|
||||
|
||||
{loading && <small>Loading...</small>}
|
||||
{!loading && renderCreated()}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
Long URL:
|
||||
|
||||
{loading && <small>Loading...</small>}
|
||||
{!loading && <a target="_blank" href={shortUrl.longUrl}>{shortUrl.longUrl}</a>}
|
||||
{!loading && <ExternalLink href={shortUrl.longUrl}>{shortUrl.longUrl}</ExternalLink>}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -159,7 +187,7 @@ export class ShortUrlsVisits extends React.Component {
|
||||
selected={this.state.startDate}
|
||||
placeholderText="Since"
|
||||
isClearable
|
||||
onChange={date => this.setState({ startDate: date }, () => this.loadVisits())}
|
||||
onChange={(date) => this.setState({ startDate: date }, () => this.loadVisits())}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-xl-3 col-lg-4 col-md-6">
|
||||
@@ -167,8 +195,8 @@ export class ShortUrlsVisits extends React.Component {
|
||||
selected={this.state.endDate}
|
||||
placeholderText="Until"
|
||||
isClearable
|
||||
onChange={date => this.setState({ endDate: date }, () => this.loadVisits())}
|
||||
className="short-url-visits__date-input"
|
||||
onChange={(date) => this.setState({ endDate: date }, () => this.loadVisits())}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -182,9 +210,12 @@ export class ShortUrlsVisits extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
ShortUrlsVisits.defaultProps = defaultProps;
|
||||
ShortUrlsVisitsComponent.propTypes = propTypes;
|
||||
ShortUrlsVisitsComponent.defaultProps = defaultProps;
|
||||
|
||||
export default connect(
|
||||
pick(['selectedServer', 'shortUrlVisits']),
|
||||
const ShortUrlsVisits = connect(
|
||||
pick([ 'selectedServer', 'shortUrlVisits' ]),
|
||||
{ getShortUrlVisits }
|
||||
)(ShortUrlsVisits);
|
||||
)(ShortUrlsVisitsComponent);
|
||||
|
||||
export default ShortUrlsVisits;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { assoc } from 'ramda';
|
||||
import Paginator from './Paginator';
|
||||
import SearchBar from './SearchBar';
|
||||
import ShortUrlsList from './ShortUrlsList';
|
||||
import { assoc } from 'ramda';
|
||||
|
||||
export function ShortUrls(props) {
|
||||
export function ShortUrlsComponent(props) {
|
||||
const { match: { params } } = props;
|
||||
|
||||
// Using a key on a component makes react to create a new instance every time the key changes
|
||||
const urlsListKey = `${params.serverId}_${params.page}`;
|
||||
|
||||
@@ -19,4 +20,8 @@ export function ShortUrls(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList))(ShortUrls);
|
||||
const ShortUrls = connect(
|
||||
(state) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList)
|
||||
)(ShortUrlsComponent);
|
||||
|
||||
export default ShortUrls;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown'
|
||||
import caretUpIcon from '@fortawesome/fontawesome-free-solid/faCaretUp'
|
||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
|
||||
import { head, isEmpty, pick, toPairs, keys, values } from 'ramda'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'
|
||||
import { ShortUrlsRow } from './helpers/ShortUrlsRow'
|
||||
import { listShortUrls } from './reducers/shortUrlsList'
|
||||
import './ShortUrlsList.scss'
|
||||
import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown';
|
||||
import caretUpIcon from '@fortawesome/fontawesome-free-solid/faCaretUp';
|
||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||
import { head, isEmpty, pick, toPairs, keys, values } from 'ramda';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
|
||||
import qs from 'qs';
|
||||
import PropTypes from 'prop-types';
|
||||
import { serverType } from '../servers/prop-types';
|
||||
import { ShortUrlsRow } from './helpers/ShortUrlsRow';
|
||||
import { listShortUrls, shortUrlType } from './reducers/shortUrlsList';
|
||||
import './ShortUrlsList.scss';
|
||||
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
|
||||
|
||||
const SORTABLE_FIELDS = {
|
||||
dateCreated: 'Created at',
|
||||
@@ -17,29 +20,43 @@ const SORTABLE_FIELDS = {
|
||||
visits: 'Visits',
|
||||
};
|
||||
|
||||
export class ShortUrlsList extends React.Component {
|
||||
refreshList = extraParams => {
|
||||
const propTypes = {
|
||||
listShortUrls: PropTypes.func,
|
||||
shortUrlsListParams: shortUrlsListParamsType,
|
||||
match: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
shortUrlsList: PropTypes.arrayOf(shortUrlType),
|
||||
selectedServer: serverType,
|
||||
};
|
||||
|
||||
export class ShortUrlsListComponent extends React.Component {
|
||||
refreshList = (extraParams) => {
|
||||
const { listShortUrls, shortUrlsListParams } = this.props;
|
||||
|
||||
listShortUrls({
|
||||
...shortUrlsListParams,
|
||||
...extraParams
|
||||
...extraParams,
|
||||
});
|
||||
};
|
||||
determineOrderDir = field => {
|
||||
determineOrderDir = (field) => {
|
||||
if (this.state.orderField !== field) {
|
||||
return 'ASC';
|
||||
}
|
||||
|
||||
const newOrderMap = {
|
||||
'ASC': 'DESC',
|
||||
'DESC': undefined,
|
||||
ASC: 'DESC',
|
||||
DESC: undefined,
|
||||
};
|
||||
|
||||
return this.state.orderDir ? newOrderMap[this.state.orderDir] : 'ASC';
|
||||
};
|
||||
orderBy = field => {
|
||||
orderBy = (field) => {
|
||||
const newOrderDir = this.determineOrderDir(field);
|
||||
|
||||
this.setState({ orderField: newOrderDir !== undefined ? field : undefined, orderDir: newOrderDir });
|
||||
this.refreshList({ orderBy: { [field]: newOrderDir } })
|
||||
this.refreshList({ orderBy: { [field]: newOrderDir } });
|
||||
};
|
||||
renderOrderIcon = (field, className = 'short-urls-list__header-icon') => {
|
||||
if (this.state.orderField !== field) {
|
||||
@@ -58,21 +75,23 @@ export class ShortUrlsList extends React.Component {
|
||||
super(props);
|
||||
|
||||
const { orderBy } = props.shortUrlsListParams;
|
||||
|
||||
this.state = {
|
||||
orderField: orderBy ? head(keys(orderBy)) : undefined,
|
||||
orderDir: orderBy ? head(values(orderBy)) : undefined,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { match: { params }, location } = this.props;
|
||||
const query = qs.parse(location.search, { ignoreQueryPrefix: true });
|
||||
|
||||
this.refreshList({ page: params.page, tags: query.tag ? [query.tag] : [] });
|
||||
this.refreshList({ page: params.page, tags: query.tag ? [ query.tag ] : [] });
|
||||
}
|
||||
|
||||
renderShortUrls() {
|
||||
const { shortUrlsList, selectedServer, loading, error, shortUrlsListParams } = this.props;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<tr>
|
||||
@@ -85,11 +104,11 @@ export class ShortUrlsList extends React.Component {
|
||||
return <tr><td colSpan="6" className="text-center">Loading...</td></tr>;
|
||||
}
|
||||
|
||||
if (! loading && isEmpty(shortUrlsList)) {
|
||||
if (!loading && isEmpty(shortUrlsList)) {
|
||||
return <tr><td colSpan="6" className="text-center">No results found</td></tr>;
|
||||
}
|
||||
|
||||
return shortUrlsList.map(shortUrl => (
|
||||
return shortUrlsList.map((shortUrl) => (
|
||||
<ShortUrlsRow
|
||||
shortUrl={shortUrl}
|
||||
selectedServer={selectedServer}
|
||||
@@ -108,11 +127,12 @@ export class ShortUrlsList extends React.Component {
|
||||
Order by
|
||||
</DropdownToggle>
|
||||
<DropdownMenu className="short-urls-list__order-dropdown">
|
||||
{toPairs(SORTABLE_FIELDS).map(([key, value]) =>
|
||||
{toPairs(SORTABLE_FIELDS).map(([ key, value ]) => (
|
||||
<DropdownItem key={key} active={this.state.orderField === key} onClick={() => this.orderBy(key)}>
|
||||
{value}
|
||||
{this.renderOrderIcon(key, 'short-urls-list__header-icon--mobile')}
|
||||
</DropdownItem>)}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</UncontrolledDropdown>
|
||||
</div>
|
||||
@@ -166,4 +186,11 @@ export class ShortUrlsList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(pick(['selectedServer', 'shortUrlsListParams']), { listShortUrls })(ShortUrlsList);
|
||||
ShortUrlsListComponent.propTypes = propTypes;
|
||||
|
||||
const ShortUrlsList = connect(
|
||||
pick([ 'selectedServer', 'shortUrlsListParams' ]),
|
||||
{ listShortUrls }
|
||||
)(ShortUrlsListComponent);
|
||||
|
||||
export default ShortUrlsList;
|
||||
|
||||
@@ -3,8 +3,18 @@ import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||
import { isNil } from 'ramda';
|
||||
import React from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import './CreateShortUrlResult.scss'
|
||||
import { Card, CardBody, Tooltip } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { createShortUrlResultType } from '../reducers/shortUrlCreationResult';
|
||||
import './CreateShortUrlResult.scss';
|
||||
|
||||
const TIME_TO_SHOW_COPY_TOOLTIP = 2000;
|
||||
|
||||
const propTypes = {
|
||||
resetCreateShortUrl: PropTypes.func,
|
||||
error: PropTypes.bool,
|
||||
result: createShortUrlResultType,
|
||||
};
|
||||
|
||||
export default class CreateShortUrlResult extends React.Component {
|
||||
state = { showCopyTooltip: false };
|
||||
@@ -30,7 +40,7 @@ export default class CreateShortUrlResult extends React.Component {
|
||||
const { shortUrl } = result;
|
||||
const onCopy = () => {
|
||||
this.setState({ showCopyTooltip: true });
|
||||
setTimeout(() => this.setState({ showCopyTooltip: false }), 2000);
|
||||
setTimeout(() => this.setState({ showCopyTooltip: false }), TIME_TO_SHOW_COPY_TOOLTIP);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -39,8 +49,12 @@ export default class CreateShortUrlResult extends React.Component {
|
||||
<b>Great!</b> The short URL is <b>{shortUrl}</b>
|
||||
|
||||
<CopyToClipboard text={shortUrl} onCopy={onCopy}>
|
||||
<button className="btn btn-light btn-sm create-short-url-result__copy-btn" id="copyBtn" type="button">
|
||||
<FontAwesomeIcon icon={copyIcon}/> Copy
|
||||
<button
|
||||
className="btn btn-light btn-sm create-short-url-result__copy-btn"
|
||||
id="copyBtn"
|
||||
type="button"
|
||||
>
|
||||
<FontAwesomeIcon icon={copyIcon} /> Copy
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
|
||||
@@ -51,4 +65,6 @@ export default class CreateShortUrlResult extends React.Component {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
CreateShortUrlResult.propTypes = propTypes;
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||
import TagsSelector from '../../utils/TagsSelector';
|
||||
import PropTypes from 'prop-types';
|
||||
import { pick } from 'ramda';
|
||||
import TagsSelector from '../../utils/TagsSelector';
|
||||
import {
|
||||
editShortUrlTags,
|
||||
resetShortUrlsTags,
|
||||
shortUrlTagsType,
|
||||
shortUrlTagsEdited
|
||||
shortUrlTagsEdited,
|
||||
} from '../reducers/shortUrlTags';
|
||||
import { pick } from 'ramda';
|
||||
import ExternalLink from '../../utils/ExternalLink';
|
||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||
|
||||
const propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
shortUrl: PropTypes.shape({
|
||||
tags: PropTypes.arrayOf(PropTypes.string),
|
||||
shortCode: PropTypes.string,
|
||||
}).isRequired,
|
||||
shortUrl: shortUrlType.isRequired,
|
||||
shortUrlTags: shortUrlTagsType,
|
||||
editShortUrlTags: PropTypes.func,
|
||||
shortUrlTagsEdited: PropTypes.func,
|
||||
resetShortUrlsTags: PropTypes.func,
|
||||
};
|
||||
|
||||
export class EditTagsModal extends React.Component {
|
||||
export class EditTagsModalComponent extends React.Component {
|
||||
saveTags = () => {
|
||||
const { editShortUrlTags, shortUrl, toggle } = this.props;
|
||||
|
||||
editShortUrlTags(shortUrl.shortCode, this.state.tags)
|
||||
.then(() => {
|
||||
this.tagsSaved = true;
|
||||
@@ -39,11 +42,13 @@ export class EditTagsModal extends React.Component {
|
||||
|
||||
const { shortUrlTagsEdited, shortUrl } = this.props;
|
||||
const { tags } = this.state;
|
||||
|
||||
shortUrlTagsEdited(shortUrl.shortCode, tags);
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { resetShortUrlsTags } = this.props;
|
||||
|
||||
resetShortUrlsTags();
|
||||
this.tagsSaved = false;
|
||||
}
|
||||
@@ -57,12 +62,12 @@ export class EditTagsModal extends React.Component {
|
||||
const { isOpen, toggle, url, shortUrlTags } = this.props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} toggle={toggle} centered onClosed={this.refreshShortUrls}>
|
||||
<Modal isOpen={isOpen} toggle={toggle} centered onClosed={() => this.refreshShortUrls}>
|
||||
<ModalHeader toggle={toggle}>
|
||||
Edit tags for <a target="_blank" href={url}>{url}</a>
|
||||
Edit tags for <ExternalLink href={url}>{url}</ExternalLink>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<TagsSelector tags={this.state.tags} onChange={tags => this.setState({ tags })} />
|
||||
<TagsSelector tags={this.state.tags} onChange={(tags) => this.setState({ tags })} />
|
||||
{shortUrlTags.error && (
|
||||
<div className="p-2 mt-2 bg-danger text-white text-center">
|
||||
Something went wrong while saving the tags :(
|
||||
@@ -74,8 +79,8 @@ export class EditTagsModal extends React.Component {
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={this.saveTags}
|
||||
disabled={shortUrlTags.saving}
|
||||
onClick={() => this.saveTags}
|
||||
>
|
||||
{shortUrlTags.saving ? 'Saving tags...' : 'Save tags'}
|
||||
</button>
|
||||
@@ -85,9 +90,11 @@ export class EditTagsModal extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
EditTagsModal.propTypes = propTypes;
|
||||
EditTagsModalComponent.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
pick(['shortUrlTags']),
|
||||
const EditTagsModal = connect(
|
||||
pick([ 'shortUrlTags' ]),
|
||||
{ editShortUrlTags, resetShortUrlsTags, shortUrlTagsEdited }
|
||||
)(EditTagsModal);
|
||||
)(EditTagsModalComponent);
|
||||
|
||||
export default EditTagsModal;
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import './PreviewModal.scss';
|
||||
import ExternalLink from '../../utils/ExternalLink';
|
||||
|
||||
export default function PreviewModal ({ url, toggle, isOpen }) {
|
||||
const propTypes = {
|
||||
url: PropTypes.string,
|
||||
toggle: PropTypes.func,
|
||||
isOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default function PreviewModal({ url, toggle, isOpen }) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} toggle={toggle} size="lg">
|
||||
<ModalHeader toggle={toggle}>Preview for <a target="_blank" href={url}>{url}</a></ModalHeader>
|
||||
<ModalHeader toggle={toggle}>
|
||||
Preview for <ExternalLink href={url}>{url}</ExternalLink>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="text-center">
|
||||
<p className="preview-modal__loader">Loading...</p>
|
||||
@@ -15,3 +25,5 @@ export default function PreviewModal ({ url, toggle, isOpen }) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
PreviewModal.propTypes = propTypes;
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import './QrCodeModal.scss';
|
||||
import ExternalLink from '../../utils/ExternalLink';
|
||||
|
||||
export default function QrCodeModal ({ url, toggle, isOpen }) {
|
||||
const propTypes = {
|
||||
url: PropTypes.string,
|
||||
toggle: PropTypes.func,
|
||||
isOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default function QrCodeModal({ url, toggle, isOpen }) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} toggle={toggle} centered>
|
||||
<ModalHeader toggle={toggle}>QR code for <a target="_blank" href={url}>{url}</a></ModalHeader>
|
||||
<ModalHeader toggle={toggle}>
|
||||
QR code for <ExternalLink href={url}>{url}</ExternalLink>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="text-center">
|
||||
<img src={`${url}/qr-code`} className="qr-code-modal__img" alt="QR code" />
|
||||
@@ -14,3 +24,5 @@ export default function QrCodeModal ({ url, toggle, isOpen }) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
QrCodeModal.propTypes = propTypes;
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
import { isEmpty } from 'ramda';
|
||||
import React from 'react';
|
||||
import Moment from 'react-moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import Tag from '../../utils/Tag';
|
||||
import './ShortUrlsRow.scss';
|
||||
import { shortUrlsListParamsType } from '../reducers/shortUrlsListParams';
|
||||
import { serverType } from '../../servers/prop-types';
|
||||
import ExternalLink from '../../utils/ExternalLink';
|
||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||
import { ShortUrlsRowMenu } from './ShortUrlsRowMenu';
|
||||
import './ShortUrlsRow.scss';
|
||||
|
||||
const COPIED_MSG_TIME = 2000;
|
||||
|
||||
const propTypes = {
|
||||
refreshList: PropTypes.func,
|
||||
shortUrlsListParams: shortUrlsListParamsType,
|
||||
selectedServer: serverType,
|
||||
shortUrl: shortUrlType,
|
||||
};
|
||||
|
||||
export class ShortUrlsRow extends React.Component {
|
||||
state = { copiedToClipboard: false };
|
||||
@@ -15,7 +29,8 @@ export class ShortUrlsRow extends React.Component {
|
||||
|
||||
const { refreshList, shortUrlsListParams } = this.props;
|
||||
const selectedTags = shortUrlsListParams.tags || [];
|
||||
return tags.map(tag => (
|
||||
|
||||
return tags.map((tag) => (
|
||||
<Tag
|
||||
key={tag}
|
||||
text={tag}
|
||||
@@ -34,10 +49,10 @@ export class ShortUrlsRow extends React.Component {
|
||||
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
||||
</td>
|
||||
<td className="short-urls-row__cell" data-th="Short URL: ">
|
||||
<a href={completeShortUrl} target="_blank">{completeShortUrl}</a>
|
||||
<ExternalLink href={completeShortUrl}>{completeShortUrl}</ExternalLink>
|
||||
</td>
|
||||
<td className="short-urls-row__cell short-urls-row__cell--break" data-th="Long URL: ">
|
||||
<a href={shortUrl.originalUrl} target="_blank">{shortUrl.originalUrl}</a>
|
||||
<ExternalLink href={shortUrl.originalUrl}>{shortUrl.originalUrl}</ExternalLink>
|
||||
</td>
|
||||
<td className="short-urls-row__cell" data-th="Tags: ">{this.renderTags(shortUrl.tags)}</td>
|
||||
<td className="short-urls-row__cell text-md-right" data-th="Visits: ">{shortUrl.visitsCount}</td>
|
||||
@@ -54,7 +69,7 @@ export class ShortUrlsRow extends React.Component {
|
||||
shortUrl={shortUrl}
|
||||
onCopyToClipboard={() => {
|
||||
this.setState({ copiedToClipboard: true });
|
||||
setTimeout(() => this.setState({ copiedToClipboard: false }), 2000);
|
||||
setTimeout(() => this.setState({ copiedToClipboard: false }), COPIED_MSG_TIME);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
@@ -62,3 +77,5 @@ export class ShortUrlsRow extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShortUrlsRow.propTypes = propTypes;
|
||||
|
||||
@@ -9,11 +9,21 @@ import React from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { serverType } from '../../servers/prop-types';
|
||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||
import PreviewModal from './PreviewModal';
|
||||
import QrCodeModal from './QrCodeModal';
|
||||
import './ShortUrlsRowMenu.scss';
|
||||
import EditTagsModal from './EditTagsModal';
|
||||
|
||||
const propTypes = {
|
||||
completeShortUrl: PropTypes.string,
|
||||
onCopyToClipboard: PropTypes.func,
|
||||
selectedServer: serverType,
|
||||
shortUrl: shortUrlType,
|
||||
};
|
||||
|
||||
export class ShortUrlsRowMenu extends React.Component {
|
||||
state = {
|
||||
isOpen: false,
|
||||
@@ -21,26 +31,26 @@ export class ShortUrlsRowMenu extends React.Component {
|
||||
isPreviewOpen: false,
|
||||
isTagsModalOpen: false,
|
||||
};
|
||||
toggle = () => this.setState({ isOpen: !this.state.isOpen });
|
||||
toggle = () => this.setState(({ isOpen }) => ({ isOpen: !isOpen }));
|
||||
|
||||
render() {
|
||||
const { completeShortUrl, onCopyToClipboard, selectedServer, shortUrl } = this.props;
|
||||
const serverId = selectedServer ? selectedServer.id : '';
|
||||
const toggleQrCode = () => this.setState({isQrModalOpen: !this.state.isQrModalOpen});
|
||||
const togglePreview = () => this.setState({isPreviewOpen: !this.state.isPreviewOpen});
|
||||
const toggleTags = () => this.setState({isTagsModalOpen: !this.state.isTagsModalOpen});
|
||||
const toggleQrCode = () => this.setState(({ isQrModalOpen }) => ({ isQrModalOpen: !isQrModalOpen }));
|
||||
const togglePreview = () => this.setState(({ isPreviewOpen }) => ({ isPreviewOpen: !isPreviewOpen }));
|
||||
const toggleTags = () => this.setState(({ isTagsModalOpen }) => ({ isTagsModalOpen: !isTagsModalOpen }));
|
||||
|
||||
return (
|
||||
<ButtonDropdown toggle={this.toggle} isOpen={this.state.isOpen} direction="left">
|
||||
<DropdownToggle size="sm" caret className="short-urls-row-menu__dropdown-toggle btn-outline-secondary">
|
||||
<FontAwesomeIcon icon={menuIcon}/>
|
||||
<FontAwesomeIcon icon={menuIcon} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
<DropdownItem tag={Link} to={`/server/${serverId}/short-code/${shortUrl.shortCode}/visits`}>
|
||||
<FontAwesomeIcon icon={pieChartIcon}/> Visit Stats
|
||||
<FontAwesomeIcon icon={pieChartIcon} /> Visit Stats
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={toggleTags}>
|
||||
<FontAwesomeIcon icon={tagsIcon}/> Edit tags
|
||||
<FontAwesomeIcon icon={tagsIcon} /> Edit tags
|
||||
</DropdownItem>
|
||||
<EditTagsModal
|
||||
url={completeShortUrl}
|
||||
@@ -49,10 +59,10 @@ export class ShortUrlsRowMenu extends React.Component {
|
||||
toggle={toggleTags}
|
||||
/>
|
||||
|
||||
<DropdownItem divider/>
|
||||
<DropdownItem divider />
|
||||
|
||||
<DropdownItem onClick={togglePreview}>
|
||||
<FontAwesomeIcon icon={pictureIcon}/> Preview
|
||||
<FontAwesomeIcon icon={pictureIcon} /> Preview
|
||||
</DropdownItem>
|
||||
<PreviewModal
|
||||
url={completeShortUrl}
|
||||
@@ -61,7 +71,7 @@ export class ShortUrlsRowMenu extends React.Component {
|
||||
/>
|
||||
|
||||
<DropdownItem onClick={toggleQrCode}>
|
||||
<FontAwesomeIcon icon={qrIcon}/> QR code
|
||||
<FontAwesomeIcon icon={qrIcon} /> QR code
|
||||
</DropdownItem>
|
||||
<QrCodeModal
|
||||
url={completeShortUrl}
|
||||
@@ -69,11 +79,11 @@ export class ShortUrlsRowMenu extends React.Component {
|
||||
toggle={toggleQrCode}
|
||||
/>
|
||||
|
||||
<DropdownItem divider/>
|
||||
<DropdownItem divider />
|
||||
|
||||
<CopyToClipboard text={completeShortUrl} onCopy={onCopyToClipboard}>
|
||||
<DropdownItem>
|
||||
<FontAwesomeIcon icon={copyIcon}/> Copy to clipboard
|
||||
<FontAwesomeIcon icon={copyIcon} /> Copy to clipboard
|
||||
</DropdownItem>
|
||||
</CopyToClipboard>
|
||||
</DropdownMenu>
|
||||
@@ -81,3 +91,5 @@ export class ShortUrlsRowMenu extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShortUrlsRowMenu.propTypes = propTypes;
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import { curry } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import shlinkApiClient from '../../api/ShlinkApiClient';
|
||||
|
||||
const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START';
|
||||
const CREATE_SHORT_URL_ERROR = 'shlink/createShortUrl/CREATE_SHORT_URL_ERROR';
|
||||
const CREATE_SHORT_URL = 'shlink/createShortUrl/CREATE_SHORT_URL';
|
||||
const RESET_CREATE_SHORT_URL = 'shlink/createShortUrl/RESET_CREATE_SHORT_URL';
|
||||
|
||||
export const createShortUrlResultType = {
|
||||
result: PropTypes.shape({
|
||||
shortUrl: PropTypes.string,
|
||||
}),
|
||||
saving: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
result: null,
|
||||
saving: false,
|
||||
@@ -38,16 +47,18 @@ export default function reducer(state = defaultState, action) {
|
||||
}
|
||||
}
|
||||
|
||||
export const _createShortUrl = (ShlinkApiClient, data) => async dispatch => {
|
||||
export const _createShortUrl = (shlinkApiClient, data) => async (dispatch) => {
|
||||
dispatch({ type: CREATE_SHORT_URL_START });
|
||||
|
||||
try {
|
||||
const result = await ShlinkApiClient.createShortUrl(data);
|
||||
const result = await shlinkApiClient.createShortUrl(data);
|
||||
|
||||
dispatch({ type: CREATE_SHORT_URL, result });
|
||||
} catch (e) {
|
||||
dispatch({ type: CREATE_SHORT_URL_ERROR });
|
||||
}
|
||||
};
|
||||
export const createShortUrl = curry(_createShortUrl)(ShlinkApiClient);
|
||||
|
||||
export const createShortUrl = curry(_createShortUrl)(shlinkApiClient);
|
||||
|
||||
export const resetCreateShortUrl = () => ({ type: RESET_CREATE_SHORT_URL });
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import { curry } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import shlinkApiClient from '../../api/ShlinkApiClient';
|
||||
|
||||
export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START';
|
||||
|
||||
export const EDIT_SHORT_URL_TAGS_ERROR = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_ERROR';
|
||||
|
||||
export const EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS';
|
||||
|
||||
export const RESET_EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/RESET_EDIT_SHORT_URL_TAGS';
|
||||
|
||||
export const SHORT_URL_TAGS_EDITED = 'shlink/shortUrlTags/SHORT_URL_TAGS_EDITED';
|
||||
|
||||
export const shortUrlTagsType = PropTypes.shape({
|
||||
@@ -50,19 +54,21 @@ export default function reducer(state = defaultState, action) {
|
||||
}
|
||||
}
|
||||
|
||||
export const _editShortUrlTags = (ShlinkApiClient, shortCode, tags) => async (dispatch, getState) => {
|
||||
export const _editShortUrlTags = (shlinkApiClient, shortCode, tags) => async (dispatch) => {
|
||||
dispatch({ type: EDIT_SHORT_URL_TAGS_START });
|
||||
|
||||
try {
|
||||
// Update short URL tags
|
||||
await ShlinkApiClient.updateShortUrlTags(shortCode, tags);
|
||||
await shlinkApiClient.updateShortUrlTags(shortCode, tags);
|
||||
dispatch({ tags, shortCode, type: EDIT_SHORT_URL_TAGS });
|
||||
} catch (e) {
|
||||
dispatch({ type: EDIT_SHORT_URL_TAGS_ERROR });
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
export const editShortUrlTags = curry(_editShortUrlTags)(ShlinkApiClient);
|
||||
|
||||
export const editShortUrlTags = curry(_editShortUrlTags)(shlinkApiClient);
|
||||
|
||||
export const resetShortUrlsTags = () => ({ type: RESET_EDIT_SHORT_URL_TAGS });
|
||||
|
||||
|
||||
@@ -1,50 +1,60 @@
|
||||
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import { curry } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import shlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import { shortUrlType } from './shortUrlsList';
|
||||
|
||||
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';
|
||||
|
||||
export const shortUrlVisitsType = {
|
||||
shortUrl: shortUrlType,
|
||||
visits: PropTypes.array,
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
shortUrl: {},
|
||||
visits: [],
|
||||
loading: false,
|
||||
error: false
|
||||
error: false,
|
||||
};
|
||||
|
||||
export default function dispatch (state = initialState, action) {
|
||||
export default function dispatch(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GET_SHORT_URL_VISITS_START:
|
||||
return {
|
||||
...state,
|
||||
loading: true
|
||||
loading: true,
|
||||
};
|
||||
case GET_SHORT_URL_VISITS_ERROR:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: true
|
||||
error: true,
|
||||
};
|
||||
case GET_SHORT_URL_VISITS:
|
||||
return {
|
||||
shortUrl: action.shortUrl,
|
||||
visits: action.visits,
|
||||
loading: false,
|
||||
error: false
|
||||
error: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const _getShortUrlVisits = (ShlinkApiClient, shortCode, dates) => dispatch => {
|
||||
export const _getShortUrlVisits = (shlinkApiClient, shortCode, dates) => (dispatch) => {
|
||||
dispatch({ type: GET_SHORT_URL_VISITS_START });
|
||||
|
||||
Promise.all([
|
||||
ShlinkApiClient.getShortUrlVisits(shortCode, dates),
|
||||
ShlinkApiClient.getShortUrl(shortCode)
|
||||
shlinkApiClient.getShortUrlVisits(shortCode, dates),
|
||||
shlinkApiClient.getShortUrl(shortCode),
|
||||
])
|
||||
.then(([visits, shortUrl]) => dispatch({ visits, shortUrl, type: GET_SHORT_URL_VISITS }))
|
||||
.then(([ visits, shortUrl ]) => dispatch({ visits, shortUrl, type: GET_SHORT_URL_VISITS }))
|
||||
.catch(() => dispatch({ type: GET_SHORT_URL_VISITS_ERROR }));
|
||||
};
|
||||
export const getShortUrlVisits = curry(_getShortUrlVisits)(ShlinkApiClient);
|
||||
|
||||
export const getShortUrlVisits = curry(_getShortUrlVisits)(shlinkApiClient);
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import { SHORT_URL_TAGS_EDITED } from './shortUrlTags';
|
||||
import { assoc, assocPath } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import shlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import { SHORT_URL_TAGS_EDITED } from './shortUrlTags';
|
||||
|
||||
const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START';
|
||||
const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR';
|
||||
|
||||
export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
|
||||
|
||||
export const shortUrlType = PropTypes.shape({
|
||||
tags: PropTypes.arrayOf(PropTypes.string),
|
||||
shortCode: PropTypes.string,
|
||||
originalUrl: PropTypes.string,
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
shortUrls: {},
|
||||
loading: true,
|
||||
@@ -19,34 +27,36 @@ export default function reducer(state = initialState, action) {
|
||||
return {
|
||||
loading: false,
|
||||
error: false,
|
||||
shortUrls: action.shortUrls
|
||||
shortUrls: action.shortUrls,
|
||||
};
|
||||
case LIST_SHORT_URLS_ERROR:
|
||||
return {
|
||||
loading: false,
|
||||
error: true,
|
||||
shortUrls: []
|
||||
shortUrls: [],
|
||||
};
|
||||
case SHORT_URL_TAGS_EDITED:
|
||||
const { data } = state.shortUrls;
|
||||
return assocPath(['shortUrls', 'data'], data.map(shortUrl =>
|
||||
|
||||
return assocPath([ 'shortUrls', 'data' ], data.map((shortUrl) =>
|
||||
shortUrl.shortCode === action.shortCode
|
||||
? assoc('tags', action.tags, shortUrl)
|
||||
: shortUrl
|
||||
), state);
|
||||
: shortUrl), state);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const _listShortUrls = (ShlinkApiClient, params = {}) => async dispatch => {
|
||||
export const _listShortUrls = (shlinkApiClient, params = {}) => async (dispatch) => {
|
||||
dispatch({ type: LIST_SHORT_URLS_START });
|
||||
|
||||
try {
|
||||
const shortUrls = await ShlinkApiClient.listShortUrls(params);
|
||||
const shortUrls = await shlinkApiClient.listShortUrls(params);
|
||||
|
||||
dispatch({ type: LIST_SHORT_URLS, shortUrls, params });
|
||||
} catch (e) {
|
||||
dispatch({ type: LIST_SHORT_URLS_ERROR, params });
|
||||
}
|
||||
};
|
||||
export const listShortUrls = (params = {}) => _listShortUrls(ShlinkApiClient, params);
|
||||
|
||||
export const listShortUrls = (params = {}) => _listShortUrls(shlinkApiClient, params);
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { LIST_SHORT_URLS } from './shortUrlsList';
|
||||
|
||||
export const RESET_SHORT_URL_PARAMS = 'shlink/shortUrlsListParams/RESET_SHORT_URL_PARAMS';
|
||||
|
||||
export const shortUrlsListParamsType = {
|
||||
page: PropTypes.string,
|
||||
tags: PropTypes.arrayOf(PropTypes.string),
|
||||
searchTerm: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultState = { page: '1' };
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
|
||||
Reference in New Issue
Block a user