Extracted cards in overview to their own component

This commit is contained in:
Alejandro Celaya 2022-02-05 10:04:34 +01:00
parent 1011b062ae
commit e0d43020dc
6 changed files with 113 additions and 35 deletions

View file

@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames'; import classNames from 'classnames';
import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { useSwipeable, useToggle } from '../utils/helpers/hooks'; import { useSwipeable, useToggle } from '../utils/helpers/hooks';
import { supportsDomainRedirects, supportsOrphanVisits } from '../utils/helpers/features'; import { supportsDomainRedirects, supportsNonOrphanVisits, supportsOrphanVisits } from '../utils/helpers/features';
import { isReachableServer } from '../servers/data'; import { isReachableServer } from '../servers/data';
import NotFound from './NotFound'; import NotFound from './NotFound';
import { AsideMenuProps } from './AsideMenu'; import { AsideMenuProps } from './AsideMenu';
@ -33,6 +33,7 @@ const MenuLayout = (
} }
const addOrphanVisitsRoute = supportsOrphanVisits(selectedServer); const addOrphanVisitsRoute = supportsOrphanVisits(selectedServer);
const addNonOrphanVisitsRoute = supportsNonOrphanVisits(selectedServer);
const addManageDomainsRoute = supportsDomainRedirects(selectedServer); const addManageDomainsRoute = supportsDomainRedirects(selectedServer);
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible }); const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
const swipeableProps = useSwipeable(showSidebar, hideSidebar); const swipeableProps = useSwipeable(showSidebar, hideSidebar);
@ -55,6 +56,7 @@ const MenuLayout = (
<Route path="/server/:serverId/short-code/:shortCode/edit" component={EditShortUrl} /> <Route path="/server/:serverId/short-code/:shortCode/edit" component={EditShortUrl} />
<Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} /> <Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} />
{addOrphanVisitsRoute && <Route path="/server/:serverId/orphan-visits" component={OrphanVisits} />} {addOrphanVisitsRoute && <Route path="/server/:serverId/orphan-visits" component={OrphanVisits} />}
{addNonOrphanVisitsRoute && <Route path="/server/:serverId/visits" render={() => 'Non orphan'} />}
<Route exact path="/server/:serverId/manage-tags" component={TagsList} /> <Route exact path="/server/:serverId/manage-tags" component={TagsList} />
{addManageDomainsRoute && <Route exact path="/server/:serverId/manage-domains" component={ManageDomains} />} {addManageDomainsRoute && <Route exact path="/server/:serverId/manage-domains" component={ManageDomains} />}
<Route <Route

View file

@ -1,5 +1,5 @@
import { FC, useEffect } from 'react'; import { FC, useEffect } from 'react';
import { Card, CardBody, CardHeader, CardText, CardTitle, Row } from 'reactstrap'; import { Card, CardBody, CardHeader, Row } from 'reactstrap';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { ITEMS_IN_OVERVIEW_PAGE, ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList'; import { ITEMS_IN_OVERVIEW_PAGE, ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList';
import { prettify } from '../utils/helpers/numbers'; import { prettify } from '../utils/helpers/numbers';
@ -11,8 +11,9 @@ import { VisitsOverview } from '../visits/reducers/visitsOverview';
import { Versions } from '../utils/helpers/version'; import { Versions } from '../utils/helpers/version';
import { Topics } from '../mercure/helpers/Topics'; import { Topics } from '../mercure/helpers/Topics';
import { ShlinkShortUrlsListParams } from '../api/types'; import { ShlinkShortUrlsListParams } from '../api/types';
import { supportsNonOrphanVisits, supportsOrphanVisits } from '../utils/helpers/features';
import { getServerId, SelectedServer } from './data'; import { getServerId, SelectedServer } from './data';
import './Overview.scss'; import { HighlightCard } from './helpers/HighlightCard';
interface OverviewConnectProps { interface OverviewConnectProps {
shortUrlsList: ShortUrlsListState; shortUrlsList: ShortUrlsListState;
@ -41,6 +42,8 @@ export const Overview = (
const { loading: loadingTags } = tagsList; const { loading: loadingTags } = tagsList;
const { loading: loadingVisits, visitsCount, orphanVisitsCount } = visitsOverview; const { loading: loadingVisits, visitsCount, orphanVisitsCount } = visitsOverview;
const serverId = getServerId(selectedServer); const serverId = getServerId(selectedServer);
const linkToOrphanVisits = supportsOrphanVisits(selectedServer);
const linkToNonOrphanVisits = supportsNonOrphanVisits(selectedServer);
const history = useHistory(); const history = useHistory();
useEffect(() => { useEffect(() => {
@ -52,40 +55,36 @@ export const Overview = (
return ( return (
<> <>
<Row> <Row>
<div className="col-md-6 col-xl-3"> <div className="col-lg-6 col-xl-3 mb-3">
<Card className="overview__card mb-3" body> <HighlightCard title="Visits" link={linkToNonOrphanVisits ? `/server/${serverId}/visits` : undefined}>
<CardTitle tag="h5" className="overview__card-title">Visits</CardTitle> {loadingVisits ? 'Loading...' : prettify(visitsCount)}
<CardText tag="h2">{loadingVisits ? 'Loading...' : prettify(visitsCount)}</CardText> </HighlightCard>
</Card>
</div> </div>
<div className="col-md-6 col-xl-3"> <div className="col-lg-6 col-xl-3 mb-3">
<Card className="overview__card mb-3" body tag={Link} to={`/server/${serverId}/orphan-visits`}> <HighlightCard
<CardTitle tag="h5" className="overview__card-title">Orphan visits</CardTitle> title="Orphan visits"
<CardText tag="h2"> link={linkToOrphanVisits ? `/server/${serverId}/orphan-visits` : undefined}
>
<ForServerVersion minVersion="2.6.0"> <ForServerVersion minVersion="2.6.0">
{loadingVisits ? 'Loading...' : prettify(orphanVisitsCount ?? 0)} {loadingVisits ? 'Loading...' : prettify(orphanVisitsCount ?? 0)}
</ForServerVersion> </ForServerVersion>
<ForServerVersion maxVersion="2.5.*"> <ForServerVersion maxVersion="2.5.*">
<small className="text-muted"><i>Shlink 2.6 is needed</i></small> <small className="text-muted"><i>Shlink 2.6 is needed</i></small>
</ForServerVersion> </ForServerVersion>
</CardText> </HighlightCard>
</Card>
</div> </div>
<div className="col-md-6 col-xl-3"> <div className="col-lg-6 col-xl-3 mb-3">
<Card className="overview__card mb-3" body tag={Link} to={`/server/${serverId}/list-short-urls/1`}> <HighlightCard title="Short URLs" link={`/server/${serverId}/list-short-urls/1`}>
<CardTitle tag="h5" className="overview__card-title">Short URLs</CardTitle>
<CardText tag="h2">
{loading ? 'Loading...' : prettify(shortUrls?.pagination.totalItems ?? 0)} {loading ? 'Loading...' : prettify(shortUrls?.pagination.totalItems ?? 0)}
</CardText> </HighlightCard>
</Card>
</div> </div>
<div className="col-md-6 col-xl-3"> <div className="col-lg-6 col-xl-3 mb-3">
<Card className="overview__card mb-3" body tag={Link} to={`/server/${serverId}/manage-tags`}> <HighlightCard title="Tags" link={`/server/${serverId}/manage-tags`}>
<CardTitle tag="h5" className="overview__card-title">Tags</CardTitle> {loadingTags ? 'Loading...' : prettify(tagsList.tags.length)}
<CardText tag="h2">{loadingTags ? 'Loading...' : prettify(tagsList.tags.length)}</CardText> </HighlightCard>
</Card>
</div> </div>
</Row> </Row>
<Card className="mb-3"> <Card className="mb-3">
<CardHeader> <CardHeader>
<span className="d-sm-none">Create a short URL</span> <span className="d-sm-none">Create a short URL</span>

View file

@ -1,13 +1,13 @@
@import '../utils/base'; @import '../../utils/base';
.overview__card.overview__card { .highlight-card.highlight-card {
text-align: center; text-align: center;
border-top: 3px solid var(--brand-color); border-top: 3px solid var(--brand-color);
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
.overview__card-title { .highlight-card__title {
text-transform: uppercase; text-transform: uppercase;
color: $textPlaceholder; color: $textPlaceholder;
} }

View file

@ -0,0 +1,16 @@
import { FC } from 'react';
import { Card, CardText, CardTitle } from 'reactstrap';
import { Link } from 'react-router-dom';
import './HighlightCard.scss';
export interface HighlightCardProps {
title: string;
link?: string;
}
export const HighlightCard: FC<HighlightCardProps> = ({ children, title, link }) => (
<Card className="highlight-card" body {...(link && { tag: Link, to: link })}>
<CardTitle tag="h5" className="highlight-card__title">{title}</CardTitle>
<CardText tag="h2">{children}</CardText>
</Card>
);

View file

@ -25,3 +25,5 @@ export const supportsDomainRedirects = supportsQrErrorCorrection;
export const supportsForwardQuery = serverMatchesVersions({ minVersion: '2.9.0' }); export const supportsForwardQuery = serverMatchesVersions({ minVersion: '2.9.0' });
export const supportsDefaultDomainRedirectsEdition = serverMatchesVersions({ minVersion: '2.10.0' }); export const supportsDefaultDomainRedirectsEdition = serverMatchesVersions({ minVersion: '2.10.0' });
export const supportsNonOrphanVisits = serverMatchesVersions({ minVersion: '3.0.0' });

View file

@ -0,0 +1,59 @@
import { shallow, ShallowWrapper } from 'enzyme';
import { ReactNode } from 'react';
import { Card, CardText, CardTitle } from 'reactstrap';
import { Link } from 'react-router-dom';
import { HighlightCard, HighlightCardProps } from '../../../src/servers/helpers/HighlightCard';
describe('<HighlightCard />', () => {
let wrapper: ShallowWrapper;
const createWrapper = (props: HighlightCardProps & { children?: ReactNode }) => {
wrapper = shallow(<HighlightCard {...props} />);
return wrapper;
};
afterEach(() => wrapper?.unmount());
it('renders expected components', () => {
const wrapper = createWrapper({ title: 'foo' });
expect(wrapper.find(Card)).toHaveLength(1);
expect(wrapper.find(CardTitle)).toHaveLength(1);
expect(wrapper.find(CardText)).toHaveLength(1);
expect(wrapper.prop('tag')).not.toEqual(Link);
expect(wrapper.prop('to')).not.toBeDefined();
});
it.each([
[ 'foo' ],
[ 'bar' ],
[ 'baz' ],
])('renders provided title', (title) => {
const wrapper = createWrapper({ title });
const cardTitle = wrapper.find(CardTitle);
expect(cardTitle.html()).toContain(`>${title}<`);
});
it.each([
[ 'foo' ],
[ 'bar' ],
[ 'baz' ],
])('renders provided children', (children) => {
const wrapper = createWrapper({ title: 'foo', children });
const cardText = wrapper.find(CardText);
expect(cardText.html()).toContain(`>${children}<`);
});
it.each([
[ 'foo' ],
[ 'bar' ],
[ 'baz' ],
])('adds extra props when a link is provided', (link) => {
const wrapper = createWrapper({ title: 'foo', link });
expect(wrapper.prop('tag')).toEqual(Link);
expect(wrapper.prop('to')).toEqual(link);
});
});