Convert buttons to links.

This commit is contained in:
Mike Cao 2020-09-25 23:38:28 -07:00
parent 4fded49b03
commit 35b921bdb4
21 changed files with 108 additions and 79 deletions

View File

@ -5,7 +5,8 @@ import WebsiteChart from 'components/metrics/WebsiteChart';
import WorldMap from 'components/common/WorldMap'; import WorldMap from 'components/common/WorldMap';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import MenuLayout from 'components/layout/MenuLayout'; import MenuLayout from 'components/layout/MenuLayout';
import Button from 'components/common/Button'; import Link from 'components/common/Link';
import Loading from 'components/common/Loading';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteDetails.module.css'; import styles from './WebsiteDetails.module.css';
import PagesTable from './metrics/PagesTable'; import PagesTable from './metrics/PagesTable';
@ -17,8 +18,7 @@ import CountriesTable from './metrics/CountriesTable';
import EventsTable from './metrics/EventsTable'; import EventsTable from './metrics/EventsTable';
import EventsChart from './metrics/EventsChart'; import EventsChart from './metrics/EventsChart';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
import Loading from 'components/common/Loading'; import usePageQuery from 'hooks/usePageQuery';
import usePageQuery from '../hooks/usePageQuery';
const views = { const views = {
url: PagesTable, url: PagesTable,
@ -36,22 +36,21 @@ export default function WebsiteDetails({ websiteId, token }) {
const [countryData, setCountryData] = useState(); const [countryData, setCountryData] = useState();
const [eventsData, setEventsData] = useState(); const [eventsData, setEventsData] = useState();
const { const {
pathname,
resolve, resolve,
router,
query: { view }, query: { view },
} = usePageQuery(); } = usePageQuery();
const BackButton = () => ( const BackButton = () => (
<Button <Link
key="back-button" key="back-button"
className={styles.backButton} className={styles.backButton}
href="/website/[...id]"
as={resolve({ view: undefined })}
icon={<Arrow />} icon={<Arrow />}
size="xsmall" size="small"
onClick={() => router.push(pathname)}
> >
<FormattedMessage id="button.back" defaultMessage="Back" /> <FormattedMessage id="button.back" defaultMessage="Back" />
</Button> </Link>
); );
const menuOptions = [ const menuOptions = [
@ -93,7 +92,6 @@ export default function WebsiteDetails({ websiteId, token }) {
token, token,
websiteDomain: data?.domain, websiteDomain: data?.domain,
limit: 10, limit: 10,
onExpand: handleExpand,
}; };
const DetailsComponent = views[view]; const DetailsComponent = views[view];
@ -104,10 +102,6 @@ export default function WebsiteDetails({ websiteId, token }) {
} }
} }
function handleExpand(value) {
router.push(resolve({ view: value }));
}
if (!data) { if (!data) {
return null; return null;
} }

View File

@ -16,7 +16,6 @@
} }
.backButton { .backButton {
align-self: flex-start;
margin-bottom: 16px; margin-bottom: 16px;
} }

View File

@ -38,7 +38,7 @@ export default function Button({
{...props} {...props}
> >
{icon && <Icon className={styles.icon} icon={icon} size={size} />} {icon && <Icon className={styles.icon} icon={icon} size={size} />}
{children && <div>{children}</div>} {children && <div className={styles.label}>{children}</div>}
{tooltip && <ReactTooltip id={tooltipId}>{tooltip}</ReactTooltip>} {tooltip && <ReactTooltip id={tooltipId}>{tooltip}</ReactTooltip>}
</button> </button>
); );

View File

@ -10,7 +10,6 @@
border: 0; border: 0;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
white-space: nowrap;
position: relative; position: relative;
} }
@ -22,12 +21,15 @@
color: var(--gray900); color: var(--gray900);
} }
.large { .label {
font-size: var(--font-size-large); white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 300px;
} }
.medium { .large {
font-size: var(--font-size-normal); font-size: var(--font-size-large);
} }
.small { .small {
@ -65,7 +67,7 @@
background: inherit; background: inherit;
} }
.button .icon + div { .button .icon + * {
margin-left: 10px; margin-left: 10px;
} }
@ -74,7 +76,7 @@
margin-left: 10px; margin-left: 10px;
} }
.button.iconRight .icon + div { .button.iconRight .icon + * {
margin: 0; margin: 0;
} }

View File

@ -1,12 +1,23 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import NextLink from 'next/link'; import NextLink from 'next/link';
import Icon from './Icon';
import styles from './Link.module.css'; import styles from './Link.module.css';
export default function Link({ className, children, ...props }) { export default function Link({ className, icon, children, size, iconRight, ...props }) {
return ( return (
<NextLink {...props}> <NextLink {...props}>
<a className={classNames(styles.link, className)}>{children}</a> <a
className={classNames(styles.link, className, {
[styles.large]: size === 'large',
[styles.small]: size === 'small',
[styles.xsmall]: size === 'xsmall',
[styles.iconRight]: iconRight,
})}
>
{icon && <Icon className={styles.icon} icon={icon} size={size} />}
{children}
</a>
</NextLink> </NextLink>
); );
} }

View File

@ -4,6 +4,8 @@ a.link:visited {
position: relative; position: relative;
color: var(--gray900); color: var(--gray900);
text-decoration: none; text-decoration: none;
display: inline-flex;
align-items: center;
} }
a.link:before { a.link:before {
@ -21,3 +23,28 @@ a.link:hover:before {
width: 100%; width: 100%;
transition: width 100ms; transition: width 100ms;
} }
a.link.large {
font-size: var(--font-size-large);
}
a.link.small {
font-size: var(--font-size-small);
}
a.link.xsmall {
font-size: var(--font-size-xsmall);
}
a.link .icon + * {
margin-left: 10px;
}
a.link.iconRight .icon {
order: 1;
margin-left: 10px;
}
a.link.iconRight .icon + * {
margin: 0;
}

View File

@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
import { browserFilter } from 'lib/filters'; import { browserFilter } from 'lib/filters';
export default function BrowsersTable({ websiteId, token, limit, onExpand }) { export default function BrowsersTable({ websiteId, token, limit }) {
return ( return (
<MetricsTable <MetricsTable
title={<FormattedMessage id="metrics.browsers" defaultMessage="Browsers" />} title={<FormattedMessage id="metrics.browsers" defaultMessage="Browsers" />}
@ -13,7 +13,6 @@ export default function BrowsersTable({ websiteId, token, limit, onExpand }) {
token={token} token={token}
limit={limit} limit={limit}
dataFilter={browserFilter} dataFilter={browserFilter}
onExpand={onExpand}
/> />
); );
} }

View File

@ -3,13 +3,7 @@ import MetricsTable from './MetricsTable';
import { countryFilter, percentFilter } from 'lib/filters'; import { countryFilter, percentFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
export default function CountriesTable({ export default function CountriesTable({ websiteId, token, limit, onDataLoad = () => {} }) {
websiteId,
token,
limit,
onDataLoad = () => {},
onExpand,
}) {
return ( return (
<MetricsTable <MetricsTable
title={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />} title={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />}
@ -20,7 +14,6 @@ export default function CountriesTable({
limit={limit} limit={limit}
dataFilter={countryFilter} dataFilter={countryFilter}
onDataLoad={data => onDataLoad(percentFilter(data))} onDataLoad={data => onDataLoad(percentFilter(data))}
onExpand={onExpand}
/> />
); );
} }

View File

@ -4,7 +4,7 @@ import { deviceFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { getDeviceMessage } from 'components/messages'; import { getDeviceMessage } from 'components/messages';
export default function DevicesTable({ websiteId, token, limit, onExpand }) { export default function DevicesTable({ websiteId, token, limit }) {
return ( return (
<MetricsTable <MetricsTable
title={<FormattedMessage id="metrics.devices" defaultMessage="Devices" />} title={<FormattedMessage id="metrics.devices" defaultMessage="Devices" />}
@ -15,7 +15,6 @@ export default function DevicesTable({ websiteId, token, limit, onExpand }) {
limit={limit} limit={limit}
dataFilter={deviceFilter} dataFilter={deviceFilter}
renderLabel={({ x }) => getDeviceMessage(x)} renderLabel={({ x }) => getDeviceMessage(x)}
onExpand={onExpand}
/> />
); );
} }

View File

@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
import styles from './EventsTable.module.css'; import styles from './EventsTable.module.css';
export default function EventsTable({ websiteId, token, limit, onExpand, onDataLoad }) { export default function EventsTable({ websiteId, token, limit, onDataLoad }) {
return ( return (
<MetricsTable <MetricsTable
title={<FormattedMessage id="metrics.events" defaultMessage="Events" />} title={<FormattedMessage id="metrics.events" defaultMessage="Events" />}
@ -13,7 +13,6 @@ export default function EventsTable({ websiteId, token, limit, onExpand, onDataL
token={token} token={token}
limit={limit} limit={limit}
renderLabel={({ x }) => <Label value={x} />} renderLabel={({ x }) => <Label value={x} />}
onExpand={onExpand}
onDataLoad={onDataLoad} onDataLoad={onDataLoad}
/> />
); );

View File

@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import { useSpring, animated, config } from 'react-spring'; import { useSpring, animated, config } from 'react-spring';
import classNames from 'classnames'; import classNames from 'classnames';
import Button from 'components/common/Button'; import Link from 'components/common/Link';
import Loading from 'components/common/Loading'; import Loading from 'components/common/Loading';
import NoData from 'components/common/NoData'; import NoData from 'components/common/NoData';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
@ -11,8 +11,8 @@ import Arrow from 'assets/arrow-right.svg';
import { percentFilter } from 'lib/filters'; import { percentFilter } from 'lib/filters';
import { formatNumber, formatLongNumber } from 'lib/format'; import { formatNumber, formatLongNumber } from 'lib/format';
import useDateRange from 'hooks/useDateRange'; import useDateRange from 'hooks/useDateRange';
import usePageQuery from 'hooks/usePageQuery';
import styles from './MetricsTable.module.css'; import styles from './MetricsTable.module.css';
import usePageQuery from '../../hooks/usePageQuery';
export default function MetricsTable({ export default function MetricsTable({
websiteId, websiteId,
@ -27,11 +27,11 @@ export default function MetricsTable({
limit, limit,
renderLabel, renderLabel,
onDataLoad = () => {}, onDataLoad = () => {},
onExpand = () => {},
}) { }) {
const [dateRange] = useDateRange(websiteId); const [dateRange] = useDateRange(websiteId);
const { startDate, endDate, modified } = dateRange; const { startDate, endDate, modified } = dateRange;
const { const {
resolve,
query: { url }, query: { url },
} = usePageQuery(); } = usePageQuery();
@ -106,9 +106,15 @@ export default function MetricsTable({
</div> </div>
<div className={styles.footer}> <div className={styles.footer}>
{limit && ( {limit && (
<Button icon={<Arrow />} size="xsmall" onClick={() => onExpand(type)}> <Link
icon={<Arrow />}
href="/website/[...id]"
as={resolve({ view: type })}
size="small"
iconRight
>
<FormattedMessage id="button.more" defaultMessage="More" /> <FormattedMessage id="button.more" defaultMessage="More" />
</Button> </Link>
)} )}
</div> </div>
</> </>

View File

@ -1,6 +1,6 @@
.container { .container {
position: relative; position: relative;
min-height: 460px; min-height: 430px;
font-size: var(--font-size-small); font-size: var(--font-size-small);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -21,6 +21,7 @@
.metric { .metric {
font-size: var(--font-size-small); font-size: var(--font-size-small);
font-weight: 600;
text-align: center; text-align: center;
width: 100px; width: 100px;
cursor: pointer; cursor: pointer;
@ -72,8 +73,8 @@
.percent { .percent {
position: relative; position: relative;
width: 50px; width: 50px;
color: #6e6e6e; color: var(--gray600);
border-left: 1px solid var(--gray500); border-left: 1px solid var(--gray600);
padding-left: 10px; padding-left: 10px;
z-index: 1; z-index: 1;
} }

View File

@ -3,7 +3,7 @@ import MetricsTable from './MetricsTable';
import { osFilter } from 'lib/filters'; import { osFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
export default function OSTable({ websiteId, token, limit, onExpand }) { export default function OSTable({ websiteId, token, limit }) {
return ( return (
<MetricsTable <MetricsTable
title={<FormattedMessage id="metrics.operating-systems" defaultMessage="Operating system" />} title={<FormattedMessage id="metrics.operating-systems" defaultMessage="Operating system" />}
@ -13,7 +13,6 @@ export default function OSTable({ websiteId, token, limit, onExpand }) {
token={token} token={token}
limit={limit} limit={limit}
dataFilter={osFilter} dataFilter={osFilter}
onExpand={onExpand}
/> />
); );
} }

View File

@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import Link from 'next/link'; import Link from 'next/link';
import ButtonGroup from 'components/common/ButtonGroup'; import ButtonGroup from 'components/common/ButtonGroup';
import ButtonLayout from 'components/layout/ButtonLayout'; import ButtonLayout from 'components/layout/ButtonLayout';
@ -7,17 +8,14 @@ import { urlFilter } from 'lib/filters';
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants'; import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
import usePageQuery from 'hooks/usePageQuery'; import usePageQuery from 'hooks/usePageQuery';
import MetricsTable from './MetricsTable'; import MetricsTable from './MetricsTable';
import styles from './PagesTable.module.css';
export default function PagesTable({ export default function PagesTable({ websiteId, token, websiteDomain, limit, showFilters }) {
websiteId,
token,
websiteDomain,
limit,
showFilters,
onExpand,
}) {
const [filter, setFilter] = useState(FILTER_COMBINED); const [filter, setFilter] = useState(FILTER_COMBINED);
const { resolve } = usePageQuery(); const {
resolve,
query: { url },
} = usePageQuery();
const buttons = [ const buttons = [
{ {
@ -30,7 +28,14 @@ export default function PagesTable({
const renderLink = ({ x }) => { const renderLink = ({ x }) => {
return ( return (
<Link href={resolve({ url: x })} replace={true}> <Link href={resolve({ url: x })} replace={true}>
<a>{decodeURI(x)}</a> <a
className={classNames({
[styles.inactive]: url && x !== url,
[styles.active]: x === url,
})}
>
{decodeURI(x)}
</a>
</Link> </Link>
); );
}; };
@ -48,7 +53,6 @@ export default function PagesTable({
dataFilter={urlFilter} dataFilter={urlFilter}
filterOptions={{ domain: websiteDomain, raw: filter === FILTER_RAW }} filterOptions={{ domain: websiteDomain, raw: filter === FILTER_RAW }}
renderLabel={renderLink} renderLabel={renderLink}
onExpand={onExpand}
/> />
</> </>
); );

View File

@ -0,0 +1,8 @@
body .inactive {
color: var(--gray500);
}
body .active {
color: var(--gray900);
font-weight: 600;
}

View File

@ -6,14 +6,7 @@ import ButtonGroup from 'components/common/ButtonGroup';
import { FILTER_DOMAIN_ONLY, FILTER_COMBINED, FILTER_RAW } from 'lib/constants'; import { FILTER_DOMAIN_ONLY, FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
import ButtonLayout from '../layout/ButtonLayout'; import ButtonLayout from '../layout/ButtonLayout';
export default function ReferrersTable({ export default function ReferrersTable({ websiteId, websiteDomain, token, limit, showFilters }) {
websiteId,
websiteDomain,
token,
limit,
showFilters,
onExpand = () => {},
}) {
const [filter, setFilter] = useState(FILTER_COMBINED); const [filter, setFilter] = useState(FILTER_COMBINED);
const buttons = [ const buttons = [
@ -55,7 +48,6 @@ export default function ReferrersTable({
domainOnly: filter === FILTER_DOMAIN_ONLY, domainOnly: filter === FILTER_DOMAIN_ONLY,
raw: filter === FILTER_RAW, raw: filter === FILTER_RAW,
}} }}
onExpand={onExpand}
renderLabel={renderLink} renderLabel={renderLink}
/> />
</> </>

View File

@ -99,7 +99,7 @@ export default function WebsiteChart({
const PageFilter = ({ url, onClick }) => { const PageFilter = ({ url, onClick }) => {
return ( return (
<div className={classNames(styles.url, 'col-12')}> <div className={classNames(styles.url, 'col-12')}>
<Button icon={<Times />} onClick={onClick} variant="action" iconRight={true}> <Button icon={<Times />} onClick={onClick} variant="action" iconRight>
{url} {url}
</Button> </Button>
</div> </div>

View File

@ -4,7 +4,6 @@ import Link from 'components/common/Link';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
import RefreshButton from 'components/common/RefreshButton'; import RefreshButton from 'components/common/RefreshButton';
import ButtonLayout from 'components/layout/ButtonLayout'; import ButtonLayout from 'components/layout/ButtonLayout';
import Icon from 'components/common/Icon';
import ActiveUsers from './ActiveUsers'; import ActiveUsers from './ActiveUsers';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteHeader.module.css'; import styles from './WebsiteHeader.module.css';
@ -21,9 +20,11 @@ export default function WebsiteHeader({ websiteId, token, title, showLink = fals
href="/website/[...id]" href="/website/[...id]"
as={`/website/${websiteId}/${title}`} as={`/website/${websiteId}/${title}`}
className={styles.link} className={styles.link}
icon={<Arrow />}
size="small"
iconRight
> >
<FormattedMessage id="button.view-details" defaultMessage="View details" /> <FormattedMessage id="button.view-details" defaultMessage="View details" />
<Icon icon={<Arrow />} size="small" />
</Link> </Link>
)} )}
</ButtonLayout> </ButtonLayout>

View File

@ -5,14 +5,9 @@
} }
.link { .link {
font-size: var(--font-size-small);
font-weight: 600; font-weight: 600;
} }
.link svg {
margin-left: 10px;
}
@media only screen and (max-width: 576px) { @media only screen and (max-width: 576px) {
.active { .active {
display: none; display: none;

View File

@ -1,6 +1,6 @@
{ {
"name": "umami", "name": "umami",
"version": "0.53.0", "version": "0.54.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ", "description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao <mike@mikecao.com>", "author": "Mike Cao <mike@mikecao.com>",
"license": "MIT", "license": "MIT",

View File

@ -55,7 +55,7 @@ export default async (req, res) => {
getColumn(type), getColumn(type),
getTable(type), getTable(type),
domain, domain,
url, type !== 'url' ? url : undefined,
); );
return ok(res, data); return ok(res, data);