mirror of
https://github.com/umami-software/umami.git
synced 2026-02-20 12:35:38 +01:00
Merge branch 'dev' into feat/um-197-hook-up-teams
This commit is contained in:
commit
f18ee58ac8
41 changed files with 1195 additions and 875 deletions
|
|
@ -12,15 +12,15 @@ export default function FilterLink({ id, value, label, externalUrl }) {
|
|||
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<Link href={resolveUrl({ [id]: value })} replace>
|
||||
<a
|
||||
className={classNames(styles.label, {
|
||||
[styles.inactive]: active && !selected,
|
||||
[styles.active]: active && selected,
|
||||
})}
|
||||
>
|
||||
{safeDecodeURI(label || value)}
|
||||
</a>
|
||||
<Link
|
||||
href={resolveUrl({ [id]: value })}
|
||||
className={classNames(styles.label, {
|
||||
[styles.inactive]: active && !selected,
|
||||
[styles.active]: active && selected,
|
||||
})}
|
||||
replace
|
||||
>
|
||||
{safeDecodeURI(label || value)}
|
||||
</Link>
|
||||
{externalUrl && (
|
||||
<a className={styles.link} href={externalUrl} target="_blank" rel="noreferrer noopener">
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@ export default function MobileMenu({ items = [], onClose }) {
|
|||
</div>
|
||||
<div className={styles.items}>
|
||||
{items.map(({ label, value }) => (
|
||||
<Link key={value} href={value}>
|
||||
<a className={styles.item} onClick={onClose}>
|
||||
{label}
|
||||
</a>
|
||||
<Link key={value} href={value} className={styles.item} onClick={onClose}>
|
||||
{label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -7,15 +7,13 @@ export default function LogoutButton({ tooltipPosition = 'top' }) {
|
|||
const { formatMessage } = useIntl();
|
||||
return (
|
||||
<Link href="/logout">
|
||||
<a>
|
||||
<Tooltip label={formatMessage(labels.logout)} position={tooltipPosition}>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Logout />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</a>
|
||||
<Tooltip label={formatMessage(labels.logout)} position={tooltipPosition}>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Logout />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,11 @@ export default function Header() {
|
|||
<header className={styles.header}>
|
||||
<Row>
|
||||
<Column>
|
||||
<Link href="https://umami.is" target="_blank">
|
||||
<a className={styles.title}>
|
||||
<Icon size="lg">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text>umami</Text>
|
||||
</a>
|
||||
<Link href="https://umami.is" target="_blank" className={styles.title}>
|
||||
<Icon size="lg">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text>umami</Text>
|
||||
</Link>
|
||||
</Column>
|
||||
<Column className={styles.buttons}>
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import { useRouter } from 'next/router';
|
||||
import classNames from 'classnames';
|
||||
import NavMenu from 'components/common/NavMenu';
|
||||
import styles from './MenuLayout.module.css';
|
||||
|
||||
export default function MenuLayout({
|
||||
menu,
|
||||
selectedOption,
|
||||
className,
|
||||
menuClassName,
|
||||
contentClassName,
|
||||
children,
|
||||
replace = false,
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
function handleSelect(url) {
|
||||
if (replace) {
|
||||
router.replace(url, undefined, { shallow: true });
|
||||
} else {
|
||||
router.push(url, undefined, { shallow: true });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className, 'row')}>
|
||||
<NavMenu
|
||||
options={menu}
|
||||
selectedOption={selectedOption}
|
||||
className={classNames(styles.menu, menuClassName, 'col-12 col-lg-2')}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
<div className={classNames(styles.content, contentClassName, 'col-12 col-lg-10')}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.container .menu {
|
||||
padding: 30px 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.container .content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
border-left: 1px solid var(--base300);
|
||||
padding-left: 30px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.container .menu {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.container .content {
|
||||
border-top: 1px solid var(--base300);
|
||||
border-left: 0;
|
||||
padding-left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -99,6 +99,12 @@ export const labels = defineMessages({
|
|||
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
||||
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
|
||||
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' },
|
||||
bounceRate: { id: 'label.bounce-rate', defaultMessage: 'Bounce rate' },
|
||||
averageVisitTime: { id: 'label.average-visit-time', defaultMessage: 'Average visit time' },
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import { useState } from 'react';
|
||||
import { Loading } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { useIntl } from 'react-intl';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
|
||||
import MetricCard from './MetricCard';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './MetricsBar.module.css';
|
||||
|
||||
export default function MetricsBar({ websiteId, className }) {
|
||||
export default function MetricsBar({ websiteId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { get, useQuery } = useApi();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, modified } = dateRange;
|
||||
|
|
@ -52,25 +53,25 @@ export default function MetricsBar({ websiteId, className }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.bar, className)} onClick={handleSetFormat}>
|
||||
<div className={styles.bar} onClick={handleSetFormat}>
|
||||
{isLoading && !isFetched && <Loading icon="dots" />}
|
||||
{error && <ErrorMessage />}
|
||||
{data && !error && isFetched && (
|
||||
<>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.views" defaultMessage="Views" />}
|
||||
label={formatMessage(labels.views)}
|
||||
value={pageviews.value}
|
||||
change={pageviews.change}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
label={formatMessage(labels.visitors)}
|
||||
value={uniques.value}
|
||||
change={uniques.change}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
label={<FormattedMessage id="metrics.bounce-rate" defaultMessage="Bounce rate" />}
|
||||
label={formatMessage(labels.bounceRate)}
|
||||
value={uniques.value ? (num / uniques.value) * 100 : 0}
|
||||
change={
|
||||
uniques.value && uniques.change
|
||||
|
|
@ -82,12 +83,7 @@ export default function MetricsBar({ websiteId, className }) {
|
|||
reverseColors
|
||||
/>
|
||||
<MetricCard
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="metrics.average-visit-time"
|
||||
defaultMessage="Average visit time"
|
||||
/>
|
||||
}
|
||||
label={formatMessage(labels.averageVisitTime)}
|
||||
value={
|
||||
totaltime.value && pageviews.value
|
||||
? totaltime.value / (pageviews.value - bounces.value)
|
||||
|
|
|
|||
|
|
@ -80,14 +80,12 @@ export default function MetricsTable({
|
|||
<div className={styles.footer}>
|
||||
{data && !error && limit && (
|
||||
<Link href={router.pathname} as={resolveUrl({ view: type })}>
|
||||
<a>
|
||||
<Button variant="quiet">
|
||||
<Text>{formatMessage(messages.more)}</Text>
|
||||
<Icon size="sm">
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
</a>
|
||||
<Button variant="quiet">
|
||||
<Text>{formatMessage(messages.more)}</Text>
|
||||
<Icon size="sm">
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -71,14 +71,12 @@ export default function WebsiteChart({
|
|||
<WebsiteHeader websiteId={websiteId} title={title} domain={domain}>
|
||||
{showDetailsButton && (
|
||||
<Link href={`/websites/${websiteId}`}>
|
||||
<a>
|
||||
<Button>
|
||||
<Text>{formatMessage(labels.viewDetails)}</Text>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
</a>
|
||||
<Button>
|
||||
<Text>{formatMessage(labels.viewDetails)}</Text>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</WebsiteHeader>
|
||||
|
|
|
|||
|
|
@ -59,26 +59,25 @@ export default function TestConsole() {
|
|||
<Column xs="4">
|
||||
<div className={styles.header}>Page links</div>
|
||||
<div>
|
||||
<Link href={`/console/${websiteId}?page=1`}>
|
||||
<a>page one</a>
|
||||
</Link>
|
||||
<Link href={`/console/${websiteId}?page=1`}>page one</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href={`/console/${websiteId}?page=2`}>
|
||||
<a>page two</a>
|
||||
</Link>
|
||||
<Link href={`/console/${websiteId}?page=2`}>page two</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Link href={`https://www.google.com`}>
|
||||
<a className="umami--click--external-link-direct">external link (direct)</a>
|
||||
</Link>
|
||||
<a href="https://www.google.com" className="umami--click--external-link-direct">
|
||||
external link (direct)
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Link href={`https://www.google.com`}>
|
||||
<a className="umami--click--external-link-tab" target="_blank">
|
||||
external link (tab)
|
||||
</a>
|
||||
</Link>
|
||||
<a
|
||||
href="https://www.google.com"
|
||||
className="umami--click--external-link-tab"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
external link (tab)
|
||||
</a>
|
||||
</div>
|
||||
</Column>
|
||||
<Column xs="4">
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -53,14 +53,12 @@ export default function TeamsTable({ data = [], onDelete }) {
|
|||
action: (
|
||||
<Flexbox flex={1} gap={10} justifyContent="end">
|
||||
<Link href={`/settings/teams/${id}`}>
|
||||
<a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
</a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
{showDelete && (
|
||||
<ModalTrigger>
|
||||
|
|
|
|||
|
|
@ -59,15 +59,13 @@ export default function WebsiteSettings({ websiteId }) {
|
|||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
<Link href={`/websites/${websiteId}`}>
|
||||
<a target="_blank">
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.External />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</a>
|
||||
<Link href={`/analytics/websites/${websiteId}`} target="_blank">
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.External />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30 }}>
|
||||
|
|
|
|||
|
|
@ -42,24 +42,20 @@ export default function WebsitesTable({ data = [] }) {
|
|||
row.action = (
|
||||
<Flexbox flex={1} justifyContent="end" gap={10}>
|
||||
<Link href={`/settings/websites/${id}`}>
|
||||
<a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
</a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={`/websites/${id}`}>
|
||||
<a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.External />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.External />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</Flexbox>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -86,22 +86,20 @@ export default function WebsiteMenuView({ websiteId, websiteDomain }) {
|
|||
<GridRow>
|
||||
<GridColumn xs={12} sm={12} md={12} defaultSize={3} className={styles.menu}>
|
||||
<Link href={resolveUrl({ view: undefined })}>
|
||||
<a>
|
||||
<Flexbox justifyContent="center">
|
||||
<Button variant="quiet">
|
||||
<Icon rotate={180}>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.back)}</Text>
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</a>
|
||||
<Flexbox justifyContent="center">
|
||||
<Button variant="quiet">
|
||||
<Icon rotate={180}>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.back)}</Text>
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</Link>
|
||||
<Menu items={items} selectedKey={view}>
|
||||
{({ key, label }) => (
|
||||
<Item key={key} className={styles.item}>
|
||||
<Link href={resolveUrl({ view: key })} shallow={true}>
|
||||
<a>{label}</a>
|
||||
{label}
|
||||
</Link>
|
||||
</Item>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue