diff --git a/CHANGELOG.md b/CHANGELOG.md index d56f3d2a..26c70e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), #### Added * [#174](https://github.com/shlinkio/shlink-web-client/issues/174) Added complete support for Shlink v2.x together with currently supported Shlink versions. +* [#164](https://github.com/shlinkio/shlink-web-client/issues/164) Added max visits control on those URLs which have `maxVisits`. #### Changed diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js index 11822c3a..09746f36 100644 --- a/src/short-urls/ShortUrlsList.js +++ b/src/short-urls/ShortUrlsList.js @@ -18,6 +18,7 @@ export const SORTABLE_FIELDS = { visits: 'Visits', }; +// FIXME Replace with typescript: (ShortUrlsRow component) const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Component { static propTypes = { listShortUrls: PropTypes.func, diff --git a/src/short-urls/helpers/ShortUrlVisitsCount.js b/src/short-urls/helpers/ShortUrlVisitsCount.js new file mode 100644 index 00000000..32447c8a --- /dev/null +++ b/src/short-urls/helpers/ShortUrlVisitsCount.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; +import { UncontrolledTooltip } from 'reactstrap'; +import { shortUrlType } from '../reducers/shortUrlsList'; + +const propTypes = { + shortUrl: shortUrlType, +}; + +const ShortUrlVisitsCount = ({ shortUrl }) => { + const { visitsCount, meta } = shortUrl; + const maxVisits = meta && meta.maxVisits; + + if (!maxVisits) { + return {visitsCount}; + } + + return ( + + + {visitsCount} + + {' '}/ {maxVisits}{' '} + + + + + + + This short URL will not accept more than {maxVisits} visits. + + + ); +}; + +ShortUrlVisitsCount.propTypes = propTypes; + +export default ShortUrlVisitsCount; diff --git a/src/short-urls/helpers/ShortUrlsRow.js b/src/short-urls/helpers/ShortUrlsRow.js index a22f9ba2..10585db7 100644 --- a/src/short-urls/helpers/ShortUrlsRow.js +++ b/src/short-urls/helpers/ShortUrlsRow.js @@ -7,6 +7,7 @@ import { serverType } from '../../servers/prop-types'; import ExternalLink from '../../utils/ExternalLink'; import { shortUrlType } from '../reducers/shortUrlsList'; import Tag from '../../tags/helpers/Tag'; +import ShortUrlVisitsCount from './ShortUrlVisitsCount'; import './ShortUrlsRow.scss'; const ShortUrlsRow = ( @@ -56,7 +57,9 @@ const ShortUrlsRow = ( {this.renderTags(shortUrl.tags)} - {shortUrl.visitsCount} + + + - +
diff --git a/src/visits/VisitsHeader.js b/src/visits/VisitsHeader.js index 34bb0288..d631eb31 100644 --- a/src/visits/VisitsHeader.js +++ b/src/visits/VisitsHeader.js @@ -2,18 +2,16 @@ import { Card, UncontrolledTooltip } from 'reactstrap'; import Moment from 'react-moment'; import React from 'react'; import ExternalLink from '../utils/ExternalLink'; -import './VisitsHeader.scss'; +import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount'; import { shortUrlDetailType } from './reducers/shortUrlDetail'; -import { shortUrlVisitsType } from './reducers/shortUrlVisits'; +import './VisitsHeader.scss'; const propTypes = { shortUrlDetail: shortUrlDetailType.isRequired, - shortUrlVisits: shortUrlVisitsType.isRequired, }; -export default function VisitsHeader({ shortUrlDetail, shortUrlVisits }) { +export default function VisitsHeader({ shortUrlDetail }) { const { shortUrl, loading } = shortUrlDetail; - const { visits } = shortUrlVisits; const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : ''; const longLink = shortUrl && shortUrl.longUrl ? shortUrl.longUrl : ''; @@ -30,14 +28,16 @@ export default function VisitsHeader({ shortUrlDetail, shortUrlVisits }) {

- Visits: {visits.length} + + Visits:{' '} + + Visit stats for


Created: {renderDate()}
- Long URL: -   + Long URL:{' '} {loading && Loading...} {!loading && }
diff --git a/test/short-urls/helpers/ShortUrlVisitsCount.test.js b/test/short-urls/helpers/ShortUrlVisitsCount.test.js new file mode 100644 index 00000000..2700b38c --- /dev/null +++ b/test/short-urls/helpers/ShortUrlVisitsCount.test.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { UncontrolledTooltip } from 'reactstrap'; +import ShortUrlVisitsCount from '../../../src/short-urls/helpers/ShortUrlVisitsCount'; + +describe('', () => { + let wrapper; + + const createWrapper = (shortUrl) => { + wrapper = shallow(); + + return wrapper; + }; + + afterEach(() => wrapper && wrapper.unmount()); + + it('just returns visits when no maxVisits is provided', () => { + const visitsCount = 45; + const wrapper = createWrapper({ visitsCount }); + const maxVisitsHelper = wrapper.find('.short-urls-row__max-visits-control'); + const maxVisitsTooltip = wrapper.find(UncontrolledTooltip); + + expect(wrapper.html()).toEqual(`${visitsCount}`); + expect(maxVisitsHelper).toHaveLength(0); + expect(maxVisitsTooltip).toHaveLength(0); + }); + + it('displays the maximum amount of visits when present', () => { + const visitsCount = 45; + const maxVisits = 500; + const meta = { maxVisits }; + const wrapper = createWrapper({ visitsCount, meta }); + const maxVisitsHelper = wrapper.find('.short-urls-row__max-visits-control'); + const maxVisitsTooltip = wrapper.find(UncontrolledTooltip); + + expect(wrapper.html()).toContain(`/ ${maxVisits}`); + expect(maxVisitsHelper).toHaveLength(1); + expect(maxVisitsTooltip).toHaveLength(1); + }); +}); diff --git a/test/short-urls/helpers/ShortUrlsRow.test.js b/test/short-urls/helpers/ShortUrlsRow.test.js index 4c1d3be8..5be8718a 100644 --- a/test/short-urls/helpers/ShortUrlsRow.test.js +++ b/test/short-urls/helpers/ShortUrlsRow.test.js @@ -83,7 +83,7 @@ describe('', () => { it('renders visits count in fifth row', () => { const col = wrapper.find('td').at(4); - expect(col.text()).toEqual(toString(shortUrl.visitsCount)); + expect(col.html()).toContain(toString(shortUrl.visitsCount)); }); it('updates state when copied to clipboard', () => { diff --git a/test/visits/VisitsHeader.test.js b/test/visits/VisitsHeader.test.js index a0fce13f..84b287d1 100644 --- a/test/visits/VisitsHeader.test.js +++ b/test/visits/VisitsHeader.test.js @@ -11,24 +11,20 @@ describe('', () => { shortUrl: 'https://doma.in/abc123', longUrl: 'https://foo.bar/bar/foo', dateCreated: '2018-01-01T10:00:00+01:00', + visitsCount: 3, }, loading: false, }; - const shortUrlVisits = { - visits: [{}, {}, {}], - }; beforeEach(() => { - wrapper = shallow( - - ); + wrapper = shallow(); }); afterEach(() => wrapper.unmount()); it('shows the amount of visits', () => { const visitsBadge = wrapper.find('.badge'); - expect(visitsBadge.text()).toEqual(`Visits: ${shortUrlVisits.visits.length}`); + expect(visitsBadge.html()).toContain(`Visits: ${shortUrlDetail.shortUrl.visitsCount}`); }); it('shows when the URL was created', () => {