More refactoring and clean-up.

This commit is contained in:
Mike Cao 2023-02-10 03:26:57 -08:00
parent f062cdbed2
commit c815e7cd51
16 changed files with 101 additions and 134 deletions

View File

@ -1,24 +1,11 @@
import PropTypes from 'prop-types';
import ButtonLayout from 'components/layout/ButtonLayout';
import { ButtonGroup } from 'react-basics';
import { ButtonGroup, Button, Flexbox } from 'react-basics';
function FilterButtons({ buttons, selected, onClick }) {
export default function FilterButtons({ items, selected, onSelect }) {
return (
<ButtonLayout>
<ButtonGroup size="xsmall" items={buttons} selectedItem={selected} onClick={onClick} />
</ButtonLayout>
<Flexbox justifyContent="center">
<ButtonGroup items={items} selectedKey={selected} onSelect={onSelect}>
{({ key, label }) => <Button key={key}>{label}</Button>}
</ButtonGroup>
</Flexbox>
);
}
FilterButtons.propTypes = {
buttons: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.node,
value: PropTypes.any.isRequired,
}),
),
selected: PropTypes.any,
onClick: PropTypes.func,
};
export default FilterButtons;

View File

@ -1,23 +1,32 @@
import { useEffect, useRef } from 'react';
import { useMeasure, useCombinedRefs } from 'react-basics';
import classNames from 'classnames';
import useSticky from 'hooks/useSticky';
import { UI_LAYOUT_BODY } from 'lib/constants';
export default function StickyHeader({ className, stickyClassName, stickyStyle, children }) {
const { ref, isSticky } = useSticky();
const initialWidth = useRef(0);
useEffect(() => {
initialWidth.current = ref.current.clientWidth;
}, [ref]);
export default function StickyHeader({
className,
stickyClassName,
stickyStyle,
enabled = true,
children,
}) {
const { ref: scrollRef, isSticky } = useSticky({ scrollElementId: UI_LAYOUT_BODY });
const { ref: measureRef, dimensions } = useMeasure();
return (
<div
ref={ref}
data-sticky={isSticky}
className={classNames(className, { [stickyClassName]: isSticky })}
style={isSticky ? { ...stickyStyle, width: initialWidth.current } : null}
ref={measureRef}
data-sticky={enabled && isSticky}
style={enabled && isSticky ? { height: dimensions.height } : null}
>
{children}
<div
ref={scrollRef}
className={classNames(className, { [stickyClassName]: enabled && isSticky })}
style={enabled && isSticky ? { ...stickyStyle, width: dimensions.width } : null}
>
{children}
</div>
</div>
);
}

View File

@ -2,6 +2,7 @@ import { Container } from 'react-basics';
import Head from 'next/head';
import NavBar from 'components/layout/NavBar';
import useRequireLogin from 'hooks/useRequireLogin';
import { UI_LAYOUT_BODY } from 'lib/constants';
import styles from './AppLayout.module.css';
export default function AppLayout({ title, children }) {
@ -19,7 +20,7 @@ export default function AppLayout({ title, children }) {
<div className={styles.nav}>
<NavBar />
</div>
<div className={styles.body} id="layout-body">
<div className={styles.body} id={UI_LAYOUT_BODY}>
<Container>
<main>{children}</main>
</Container>

View File

@ -1,16 +0,0 @@
import classNames from 'classnames';
import styles from './ButtonLayout.module.css';
export default function ButtonLayout({ className, children, align = 'center' }) {
return (
<div
className={classNames(styles.buttons, className, {
[styles.left]: align === 'left',
[styles.center]: align === 'center',
[styles.right]: align === 'right',
})}
>
{children}
</div>
);
}

View File

@ -1,21 +0,0 @@
.buttons {
display: flex;
align-items: center;
width: 100%;
}
.buttons button + * {
margin-left: 10px;
}
.center {
justify-content: center;
}
.left {
justify-content: flex-start;
}
.right {
justify-content: flex-end;
}

View File

@ -2,7 +2,7 @@
flex: 1;
display: flex;
flex-direction: column;
padding: 30px;
background: var(--base50);
position: relative;
padding: 30px;
}

View File

@ -79,6 +79,10 @@ export const labels = defineMessages({
query: { id: 'label.query-parameters', defaultMessage: 'Query parameters' },
back: { id: 'label.back', defaultMessage: 'Back' },
visitors: { id: 'label.visitors', defaultMessage: 'Visitors' },
filterCombined: { id: 'label.filter-combined', defaultMessage: 'Combined' },
filterRaw: { id: 'label.filter-raw', defaultMessage: 'Raw' },
views: { id: 'label.views', defaultMessage: 'View' },
none: { id: 'label.none', defaultMessage: 'None' },
});
export const messages = defineMessages({

View File

@ -1,31 +1,25 @@
import { useState } from 'react';
import { useIntl, defineMessage } from 'react-intl';
import { useIntl } from 'react-intl';
import FilterLink from 'components/common/FilterLink';
import FilterButtons from 'components/common/FilterButtons';
import { urlFilter } from 'lib/filters';
import { labels } from 'components/messages';
import MetricsTable from './MetricsTable';
export const FILTER_COMBINED = 0;
export const FILTER_RAW = 1;
const messages = defineMessage({
combined: { id: 'metrics.filter.combined', defaultMessage: 'Combined' },
raw: { id: 'metrics.filter.raw', defaultMessage: 'Raw' },
pages: { id: 'metrics.pages', defaultMessage: 'Pages' },
views: { id: 'metrics.views', defaultMessage: 'View' },
});
export default function PagesTable({ websiteId, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
const { formatMessage } = useIntl();
const buttons = [
{
label: formatMessage(messages.combined),
label: formatMessage(labels.filterCombined),
value: FILTER_COMBINED,
},
{
label: formatMessage(messages.raw),
label: formatMessage(labels.filterRaw),
value: FILTER_RAW,
},
];
@ -38,9 +32,9 @@ export default function PagesTable({ websiteId, showFilters, ...props }) {
<>
{showFilters && <FilterButtons buttons={buttons} selected={filter} onClick={setFilter} />}
<MetricsTable
title={formatMessage(messages.pages)}
title={formatMessage(labels.pages)}
type="url"
metric={formatMessage(messages.views)}
metric={formatMessage(labels.views)}
websiteId={websiteId}
dataFilter={filter !== FILTER_RAW ? urlFilter : null}
renderLabel={renderLink}

View File

@ -1,50 +1,42 @@
import { useState } from 'react';
import { useIntl, defineMessages } from 'react-intl';
import { useIntl } from 'react-intl';
import MetricsTable from './MetricsTable';
import FilterButtons from 'components/common/FilterButtons';
import FilterLink from 'components/common/FilterLink';
import { refFilter } from 'lib/filters';
import { labels } from 'components/messages';
const FILTER_COMBINED = 0;
const FILTER_RAW = 1;
const messages = defineMessages({
combined: { id: 'metrics.filter.combined', defaultMessage: 'Combined' },
raw: { id: 'metrics.filter.raw', defaultMessage: 'Raw' },
referrers: { id: 'metrics.referrers', defaultMessage: 'Referrers' },
views: { id: 'metrics.views', defaultMessage: 'Views' },
none: { id: 'label.none', defaultMessage: 'None' },
});
export default function ReferrersTable({ websiteId, showFilters, ...props }) {
const [filter, setFilter] = useState(FILTER_COMBINED);
const { formatMessage } = useIntl();
const none = formatMessage(messages.none);
const buttons = [
const items = [
{
label: formatMessage(messages.combined),
label: formatMessage(labels.filterCombined),
value: FILTER_COMBINED,
},
{ label: formatMessage(messages.raw), value: FILTER_RAW },
{ label: formatMessage(labels.filterRaw), value: FILTER_RAW },
];
const renderLink = ({ w: link, x: referrer }) => {
return referrer ? (
<FilterLink id="referrer" value={referrer} externalUrl={link} />
) : (
`(${none})`
`(${formatMessage(labels.none)})`
);
};
return (
<>
{showFilters && <FilterButtons buttons={buttons} selected={filter} onClick={setFilter} />}
{showFilters && <FilterButtons items={items} selected={filter} onSelect={setFilter} />}
<MetricsTable
{...props}
title={formatMessage(messages.referrers)}
title={formatMessage(labels.referrers)}
type="referrer"
metric={formatMessage(messages.views)}
metric={formatMessage(labels.views)}
websiteId={websiteId}
dataFilter={filter !== FILTER_RAW ? refFilter : null}
renderLabel={renderLink}

View File

@ -97,16 +97,12 @@ export default function WebsiteChart({
</a>
</Link>
)}
</WebsiteHeader>
<StickyHeader
className={styles.metrics}
stickyClassName={styles.sticky}
enabled={stickyHeader}
>
<FilterTags
params={{ url, referrer, os, browser, device, country }}
onClick={handleCloseFilter}
/>
</WebsiteHeader>
<StickyHeader stickyClassName={styles.sticky} enabled={stickyHeader}>
<Row className={styles.header}>
<Column xs={12} sm={12} md={12} defaultSize={10}>
<MetricsBar websiteId={websiteId} />

View File

@ -17,15 +17,12 @@
}
.header {
min-height: 90px;
margin-bottom: 20px;
}
.metrics {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 90px;
margin-bottom: 20px;
}
.sticky {
@ -35,6 +32,7 @@
border-bottom: 1px solid var(--base300);
z-index: 3;
width: inherit;
padding-top: 20px;
}
.filter {

View File

@ -1,8 +1,8 @@
import { defineMessages, useIntl } from 'react-intl';
import MenuButton from 'components/common/MenuButton';
import Gear from 'assets/gear.svg';
import { Menu, Icon, Text, PopupTrigger, Popup, Item, Button } from 'react-basics';
import Icons from 'components/icons';
import { labels } from 'components/messages';
import { saveDashboard } from 'store/dashboard';
import { Icon } from 'react-basics';
const messages = defineMessages({
toggleCharts: { id: 'message.toggle-charts', defaultMessage: 'Toggle charts' },
@ -33,10 +33,18 @@ export default function DashboardSettingsButton() {
}
return (
<MenuButton options={menuOptions} onSelect={handleSelect} hideLabel>
<Icon>
<Gear />
</Icon>
</MenuButton>
<PopupTrigger>
<Button>
<Icon>
<Icons.Edit />
</Icon>
<Text>{formatMessage(labels.edit)}</Text>
</Button>
<Popup alignment="end">
<Menu variant="popup" items={menuOptions} onSelect={handleSelect}>
{({ label, value }) => <Item key={value}>{label}</Item>}
</Menu>
</Popup>
</PopupTrigger>
);
}

View File

@ -1,4 +1,5 @@
import { Row, Column, Menu, Item, Icon, Button } from 'react-basics';
import { useIntl } from 'react-intl';
import Link from 'next/link';
import BrowsersTable from 'components/metrics/BrowsersTable';
import CountriesTable from 'components/metrics/CountriesTable';
@ -13,7 +14,7 @@ import EventsTable from 'components/metrics/EventsTable';
import usePageQuery from 'hooks/usePageQuery';
import Icons from 'components/icons';
import { labels } from 'components/messages';
import { useIntl } from 'react-intl';
import styles from './WebsiteMenuView.module.css';
const views = {
url: PagesTable,
@ -81,8 +82,8 @@ export default function WebsiteMenuView({ websiteId, websiteDomain }) {
const DetailsComponent = views[view];
return (
<Row>
<Column>
<Row className={styles.row}>
<Column defaultSize={3} className={styles.col}>
<Button>
<Icon rotate={180}>
<Icons.ArrowRight />
@ -91,15 +92,15 @@ export default function WebsiteMenuView({ websiteId, websiteDomain }) {
</Button>
<Menu items={items}>
{({ value, label }) => (
<Link href={resolve()}>
<a>
<Item key={value}>{label}</Item>
</a>
</Link>
<Item key={value}>
<Link href={resolve()}>
<a>{label}</a>
</Link>
</Item>
)}
</Menu>
</Column>
<Column>
<Column defaultSize={9} className={styles.col}>
<DetailsComponent
websiteId={websiteId}
websiteDomain={websiteDomain}

View File

@ -0,0 +1,13 @@
.row {
border-top: 1px solid var(--base300);
}
.col {
border-left: 1px solid var(--base300);
padding: 30px;
}
.col:first-child {
padding-left: 0;
border-left: 0;
}

View File

@ -1,20 +1,19 @@
import { useState, useEffect, useRef } from 'react';
export default function useSticky(
element = document.getElementById('layout-body'),
defaultSticky = false,
) {
export default function useSticky({ scrollElementId, defaultSticky = false }) {
const [isSticky, setIsSticky] = useState(defaultSticky);
const ref = useRef(null);
const initialTop = useRef(null);
useEffect(() => {
const element = scrollElementId ? document.getElementById(scrollElementId) : window;
const handleScroll = () => {
setIsSticky(element.scrollTop > initialTop.current);
};
if (initialTop.current === null) {
initialTop.current = ref.current.offsetTop;
initialTop.current = ref?.current?.offsetTop;
}
element.addEventListener('scroll', handleScroll);
@ -22,7 +21,7 @@ export default function useSticky(
return () => {
element.removeEventListener('scroll', handleScroll);
};
}, [setIsSticky]);
}, [ref, setIsSticky]);
return { ref, isSticky };
}

View File

@ -23,6 +23,8 @@ export const DEFAULT_WEBSITE_LIMIT = 10;
export const REALTIME_RANGE = 30;
export const REALTIME_INTERVAL = 3000;
export const UI_LAYOUT_BODY = 'ui-layout-body';
export const EVENT_TYPE = {
pageView: 1,
customEvent: 2,