Updated date range handling. Fixed share page.

This commit is contained in:
Mike Cao 2023-03-08 16:37:43 -08:00
parent 3fc5f5151b
commit 696d9c978c
28 changed files with 166 additions and 280 deletions

View file

@ -12,17 +12,18 @@ export default function StickyHeader({
}) {
const { ref: scrollRef, isSticky } = useSticky({ scrollElement });
const { ref: measureRef, dimensions } = useMeasure();
const active = enabled && isSticky;
return (
<div
ref={measureRef}
data-sticky={enabled && isSticky}
style={enabled && isSticky ? { height: dimensions.height } : null}
data-sticky={active}
style={active ? { height: dimensions.height } : null}
>
<div
ref={scrollRef}
className={classNames(className, { [stickyClassName]: enabled && isSticky })}
style={enabled && isSticky ? { ...stickyStyle, width: dimensions.width } : null}
className={classNames(className, { [stickyClassName]: active })}
style={active ? { ...stickyStyle, width: dimensions.width } : null}
>
{children}
</div>

View file

@ -1,41 +0,0 @@
import { useState, useRef, useEffect } from 'react';
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return !(
rect.bottom < 0 ||
rect.right < 0 ||
rect.left > window.innerWidth ||
rect.top > window.innerHeight
);
}
export default function CheckVisible({ className, children }) {
const [visible, setVisible] = useState(false);
const ref = useRef();
useEffect(() => {
const checkPosition = () => {
if (ref.current) {
const state = isInViewport(ref.current);
if (state !== visible) {
setVisible(state);
}
}
};
checkPosition();
window.addEventListener('scroll', checkPosition);
return () => {
window.removeEventListener('scroll', checkPosition);
};
}, [visible]);
return (
<div ref={ref} className={className} data-visible={visible}>
{typeof children === 'function' ? children(visible) : children}
</div>
);
}

View file

@ -18,7 +18,7 @@ function DateFilter({ websiteId, value, className }) {
const [showPicker, setShowPicker] = useState(false);
async function handleDateChange(value) {
if (value === 'all') {
if (value === 'all' && websiteId) {
const data = await get(`/websites/${websiteId}`);
if (data) {

View file

@ -1,6 +1,6 @@
import { useIntl } from 'react-intl';
import { LoadingButton, Icon, Tooltip } from 'react-basics';
import { setDateRange } from 'store/websites';
import { setWebsiteDateRange } from 'store/websites';
import useDateRange from 'hooks/useDateRange';
import Icons from 'components/icons';
import { labels } from 'components/messages';
@ -12,9 +12,9 @@ function RefreshButton({ websiteId, isLoading }) {
function handleClick() {
if (!isLoading && dateRange) {
if (/^\d+/.test(dateRange.value)) {
setDateRange(websiteId, dateRange.value);
setWebsiteDateRange(websiteId, dateRange.value);
} else {
setDateRange(websiteId, dateRange);
setWebsiteDateRange(websiteId, dateRange);
}
}
}

View file

@ -18,7 +18,12 @@ export default function SettingsButton() {
</Icon>
</Button>
</Tooltip>
<Popup className={styles.popup} position="bottom" alignment="end">
<Popup
className={styles.popup}
position="bottom"
alignment="end"
onClick={e => e.stopPropagation()}
>
<Form>
<FormRow label={formatMessage(labels.timezone)}>
<TimezoneSetting />

View file

@ -1,80 +0,0 @@
import useConfig from 'hooks/useConfig';
import useUser from 'hooks/useUser';
import { AUTH_TOKEN } from 'lib/constants';
import { removeItem } from 'next-basics';
import { useRouter } from 'next/router';
import { useRef, useState } from 'react';
import { Button, Icon, Item, Menu, Popup, Text } from 'react-basics';
import { FormattedMessage } from 'react-intl';
import useDocumentClick from 'hooks/useDocumentClick';
import Profile from 'assets/profile.svg';
import styles from './UserButton.module.css';
export default function UserButton() {
const [show, setShow] = useState(false);
const ref = useRef();
const { user } = useUser();
const router = useRouter();
const { adminDisabled } = useConfig();
const menuOptions = [
{
label: (
<FormattedMessage
id="label.logged-in-as"
defaultMessage="Logged in as {username}"
values={{ username: <b>{user.username}</b> }}
/>
),
value: 'username',
className: styles.username,
},
{
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,
value: 'profile',
hidden: adminDisabled,
divider: true,
},
{ label: <FormattedMessage id="label.logout" defaultMessage="Logout" />, value: 'logout' },
];
function handleClick() {
setShow(state => !state);
}
function handleSelect(value) {
if (value === 'logout') {
removeItem(AUTH_TOKEN);
router.push('/login');
} else if (value === 'profile') {
router.push('/profile');
}
}
useDocumentClick(e => {
if (!ref.current?.contains(e.target)) {
setShow(false);
}
});
return (
<div className={styles.button} ref={ref}>
<Button variant="light" onClick={handleClick}>
<Icon className={styles.icon} size="large">
<Profile />
</Icon>
</Button>
{show && (
<Popup className={styles.menu} position="bottom" gap={5}>
<Menu items={menuOptions} onSelect={handleSelect}>
{({ label, value }) => (
<Item key={value}>
<Text>{label}</Text>
</Item>
)}
</Menu>
</Popup>
)}
</div>
);
}

View file

@ -1,25 +0,0 @@
.button {
position: relative;
}
.username {
border-bottom: 1px solid var(--base500);
}
.username:hover {
background: var(--base50);
}
.icon svg {
height: 16px;
width: 16px;
}
.menu {
left: -50%;
background: var(--base50);
border: 1px solid var(--base500);
border-radius: 4px;
overflow: hidden;
z-index: 100;
}

View file

@ -8,7 +8,7 @@ export default function Footer() {
return (
<footer className={styles.footer}>
<Row>
<Column defaultSize={11} xs={12} sm={12}>
<Column defaultSize={12} lg={11} xl={11}>
<div>
<FormattedMessage
{...labels.poweredBy}
@ -22,7 +22,7 @@ export default function Footer() {
/>
</div>
</Column>
<Column className={styles.version} defaultSize={1} xs={12} sm={12}>
<Column className={styles.version} defaultSize={12} lg={1} xl={1}>
<a href={REPO_URL}>{`v${CURRENT_VERSION}`}</a>
</Column>
</Row>

View file

@ -1,6 +1,7 @@
.footer {
font-size: var(--font-size-sm);
text-align: center;
line-height: 30px;
margin: 60px 0;
}
@ -11,10 +12,5 @@
.version {
text-align: right;
padding-right: 10px;
}
@media only screen and (max-width: 768px) {
.footer .version {
text-align: center;
}
white-space: nowrap;
}

View file

@ -100,6 +100,8 @@ export const labels = defineMessages({
logs: { id: 'label.activity-log', defaultMessage: 'Activity log' },
dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },
poweredBy: { id: 'label.powered-by', defaultMessage: 'Powered by {name}' },
pageViews: { id: 'label.page-views', defaultMessage: 'Page views' },
uniqueVisitors: { id: 'label.unique-visitors', defaultMessage: 'Unique visitors' },
});
export const messages = defineMessages({

View file

@ -1,9 +1,11 @@
import { useVisible } from 'react-basics';
import { useIntl } from 'react-intl';
import { colord } from 'colord';
import CheckVisible from 'components/helpers/CheckVisible';
import BarChart from './BarChart';
import useTheme from 'hooks/useTheme';
import { THEME_COLORS, DEFAULT_ANIMATION_DURATION } from 'lib/constants';
import { labels } from 'components/messages';
import { useMemo } from 'react';
export default function PageviewsChart({
websiteId,
@ -15,19 +17,23 @@ export default function PageviewsChart({
animationDuration = DEFAULT_ANIMATION_DURATION,
...props
}) {
const intl = useIntl();
const { formatMessage } = useIntl();
const [theme] = useTheme();
const primaryColor = colord(THEME_COLORS[theme].primary);
const colors = {
views: {
background: primaryColor.alpha(0.4).toRgbString(),
border: primaryColor.alpha(0.5).toRgbString(),
},
visitors: {
background: primaryColor.alpha(0.6).toRgbString(),
border: primaryColor.alpha(0.7).toRgbString(),
},
};
const { ref, visible } = useVisible();
const colors = useMemo(() => {
const primaryColor = colord(THEME_COLORS[theme].primary);
return {
views: {
background: primaryColor.alpha(0.4).toRgbString(),
border: primaryColor.alpha(0.5).toRgbString(),
},
visitors: {
background: primaryColor.alpha(0.6).toRgbString(),
border: primaryColor.alpha(0.7).toRgbString(),
},
};
}, [theme]);
const handleUpdate = chart => {
const {
@ -35,15 +41,9 @@ export default function PageviewsChart({
} = chart;
datasets[0].data = data.sessions;
datasets[0].label = intl.formatMessage({
id: 'metrics.unique-visitors',
defaultMessage: 'Unique visitors',
});
datasets[0].label = formatMessage(labels.uniqueVisitors);
datasets[1].data = data.pageviews;
datasets[1].label = intl.formatMessage({
id: 'metrics.page-views',
defaultMessage: 'Page views',
});
datasets[1].label = formatMessage(labels.pageViews);
};
if (!data) {
@ -51,43 +51,35 @@ export default function PageviewsChart({
}
return (
<CheckVisible>
{visible => (
<BarChart
{...props}
className={className}
chartId={websiteId}
datasets={[
{
label: intl.formatMessage({
id: 'metrics.unique-visitors',
defaultMessage: 'Unique visitors',
}),
data: data.sessions,
lineTension: 0,
backgroundColor: colors.visitors.background,
borderColor: colors.visitors.border,
borderWidth: 1,
},
{
label: intl.formatMessage({
id: 'metrics.page-views',
defaultMessage: 'Page views',
}),
data: data.pageviews,
lineTension: 0,
backgroundColor: colors.views.background,
borderColor: colors.views.border,
borderWidth: 1,
},
]}
unit={unit}
records={records}
animationDuration={visible ? animationDuration : 0}
onUpdate={handleUpdate}
loading={loading}
/>
)}
</CheckVisible>
<div ref={ref}>
<BarChart
{...props}
className={className}
chartId={websiteId}
datasets={[
{
label: formatMessage(labels.uniqueVisitors),
data: data.sessions,
lineTension: 0,
backgroundColor: colors.visitors.background,
borderColor: colors.visitors.border,
borderWidth: 1,
},
{
label: formatMessage(labels.pageViews),
data: data.pageviews,
lineTension: 0,
backgroundColor: colors.views.background,
borderColor: colors.views.border,
borderWidth: 1,
},
]}
unit={unit}
records={records}
animationDuration={visible ? animationDuration : 0}
onUpdate={handleUpdate}
loading={loading}
/>
</div>
);
}

View file

@ -6,7 +6,7 @@ import PageviewsChart from './PageviewsChart';
import MetricsBar from './MetricsBar';
import WebsiteHeader from './WebsiteHeader';
import DateFilter from 'components/input/DateFilter';
import StickyHeader from 'components/helpers/StickyHeader';
import StickyHeader from 'components/common/StickyHeader';
import ErrorMessage from 'components/common/ErrorMessage';
import FilterTags from 'components/metrics/FilterTags';
import RefreshButton from 'components/input/RefreshButton';

View file

@ -6,7 +6,7 @@ import firstBy from 'thenby';
import { GridRow, GridColumn } from 'components/layout/Grid';
import Page from 'components/layout/Page';
import RealtimeChart from 'components/metrics/RealtimeChart';
import StickyHeader from 'components/helpers/StickyHeader';
import StickyHeader from 'components/common/StickyHeader';
import PageHeader from 'components/layout/PageHeader';
import WorldMap from 'components/common/WorldMap';
import RealtimeLog from 'components/pages/realtime/RealtimeLog';

View file

@ -14,7 +14,7 @@ export default function DateRangeSetting() {
return (
<Flexbox width={400} gap={10}>
<DateFilter value={value} startDate={startDate} endDate={endDate} onChange={setDateRange} />
<DateFilter value={value} startDate={startDate} endDate={endDate} />
<Button onClick={handleReset}>{formatMessage(labels.reset)}</Button>
</Flexbox>
);