mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-15 09:45:04 +01:00
Updated date range handling. Fixed share page.
This commit is contained in:
parent
3fc5f5151b
commit
696d9c978c
@ -12,17 +12,18 @@ export default function StickyHeader({
|
|||||||
}) {
|
}) {
|
||||||
const { ref: scrollRef, isSticky } = useSticky({ scrollElement });
|
const { ref: scrollRef, isSticky } = useSticky({ scrollElement });
|
||||||
const { ref: measureRef, dimensions } = useMeasure();
|
const { ref: measureRef, dimensions } = useMeasure();
|
||||||
|
const active = enabled && isSticky;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={measureRef}
|
ref={measureRef}
|
||||||
data-sticky={enabled && isSticky}
|
data-sticky={active}
|
||||||
style={enabled && isSticky ? { height: dimensions.height } : null}
|
style={active ? { height: dimensions.height } : null}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
className={classNames(className, { [stickyClassName]: enabled && isSticky })}
|
className={classNames(className, { [stickyClassName]: active })}
|
||||||
style={enabled && isSticky ? { ...stickyStyle, width: dimensions.width } : null}
|
style={active ? { ...stickyStyle, width: dimensions.width } : null}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</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);
|
const [showPicker, setShowPicker] = useState(false);
|
||||||
|
|
||||||
async function handleDateChange(value) {
|
async function handleDateChange(value) {
|
||||||
if (value === 'all') {
|
if (value === 'all' && websiteId) {
|
||||||
const data = await get(`/websites/${websiteId}`);
|
const data = await get(`/websites/${websiteId}`);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { LoadingButton, Icon, Tooltip } from 'react-basics';
|
import { LoadingButton, Icon, Tooltip } from 'react-basics';
|
||||||
import { setDateRange } from 'store/websites';
|
import { setWebsiteDateRange } from 'store/websites';
|
||||||
import useDateRange from 'hooks/useDateRange';
|
import useDateRange from 'hooks/useDateRange';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import { labels } from 'components/messages';
|
import { labels } from 'components/messages';
|
||||||
@ -12,9 +12,9 @@ function RefreshButton({ websiteId, isLoading }) {
|
|||||||
function handleClick() {
|
function handleClick() {
|
||||||
if (!isLoading && dateRange) {
|
if (!isLoading && dateRange) {
|
||||||
if (/^\d+/.test(dateRange.value)) {
|
if (/^\d+/.test(dateRange.value)) {
|
||||||
setDateRange(websiteId, dateRange.value);
|
setWebsiteDateRange(websiteId, dateRange.value);
|
||||||
} else {
|
} else {
|
||||||
setDateRange(websiteId, dateRange);
|
setWebsiteDateRange(websiteId, dateRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,12 @@ export default function SettingsButton() {
|
|||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Popup className={styles.popup} position="bottom" alignment="end">
|
<Popup
|
||||||
|
className={styles.popup}
|
||||||
|
position="bottom"
|
||||||
|
alignment="end"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<FormRow label={formatMessage(labels.timezone)}>
|
<FormRow label={formatMessage(labels.timezone)}>
|
||||||
<TimezoneSetting />
|
<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 (
|
return (
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<Row>
|
<Row>
|
||||||
<Column defaultSize={11} xs={12} sm={12}>
|
<Column defaultSize={12} lg={11} xl={11}>
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
{...labels.poweredBy}
|
{...labels.poweredBy}
|
||||||
@ -22,7 +22,7 @@ export default function Footer() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Column>
|
</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>
|
<a href={REPO_URL}>{`v${CURRENT_VERSION}`}</a>
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
.footer {
|
.footer {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
line-height: 30px;
|
||||||
margin: 60px 0;
|
margin: 60px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,10 +12,5 @@
|
|||||||
.version {
|
.version {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
white-space: nowrap;
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
|
||||||
.footer .version {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,8 @@ export const labels = defineMessages({
|
|||||||
logs: { id: 'label.activity-log', defaultMessage: 'Activity log' },
|
logs: { id: 'label.activity-log', defaultMessage: 'Activity log' },
|
||||||
dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },
|
dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },
|
||||||
poweredBy: { id: 'label.powered-by', defaultMessage: 'Powered by {name}' },
|
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({
|
export const messages = defineMessages({
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import { useVisible } from 'react-basics';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { colord } from 'colord';
|
import { colord } from 'colord';
|
||||||
import CheckVisible from 'components/helpers/CheckVisible';
|
|
||||||
import BarChart from './BarChart';
|
import BarChart from './BarChart';
|
||||||
import useTheme from 'hooks/useTheme';
|
import useTheme from 'hooks/useTheme';
|
||||||
import { THEME_COLORS, DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
import { THEME_COLORS, DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||||
|
import { labels } from 'components/messages';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export default function PageviewsChart({
|
export default function PageviewsChart({
|
||||||
websiteId,
|
websiteId,
|
||||||
@ -15,19 +17,23 @@ export default function PageviewsChart({
|
|||||||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [theme] = useTheme();
|
const [theme] = useTheme();
|
||||||
const primaryColor = colord(THEME_COLORS[theme].primary);
|
const { ref, visible } = useVisible();
|
||||||
const colors = {
|
|
||||||
views: {
|
const colors = useMemo(() => {
|
||||||
background: primaryColor.alpha(0.4).toRgbString(),
|
const primaryColor = colord(THEME_COLORS[theme].primary);
|
||||||
border: primaryColor.alpha(0.5).toRgbString(),
|
return {
|
||||||
},
|
views: {
|
||||||
visitors: {
|
background: primaryColor.alpha(0.4).toRgbString(),
|
||||||
background: primaryColor.alpha(0.6).toRgbString(),
|
border: primaryColor.alpha(0.5).toRgbString(),
|
||||||
border: primaryColor.alpha(0.7).toRgbString(),
|
},
|
||||||
},
|
visitors: {
|
||||||
};
|
background: primaryColor.alpha(0.6).toRgbString(),
|
||||||
|
border: primaryColor.alpha(0.7).toRgbString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
const handleUpdate = chart => {
|
const handleUpdate = chart => {
|
||||||
const {
|
const {
|
||||||
@ -35,15 +41,9 @@ export default function PageviewsChart({
|
|||||||
} = chart;
|
} = chart;
|
||||||
|
|
||||||
datasets[0].data = data.sessions;
|
datasets[0].data = data.sessions;
|
||||||
datasets[0].label = intl.formatMessage({
|
datasets[0].label = formatMessage(labels.uniqueVisitors);
|
||||||
id: 'metrics.unique-visitors',
|
|
||||||
defaultMessage: 'Unique visitors',
|
|
||||||
});
|
|
||||||
datasets[1].data = data.pageviews;
|
datasets[1].data = data.pageviews;
|
||||||
datasets[1].label = intl.formatMessage({
|
datasets[1].label = formatMessage(labels.pageViews);
|
||||||
id: 'metrics.page-views',
|
|
||||||
defaultMessage: 'Page views',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@ -51,43 +51,35 @@ export default function PageviewsChart({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CheckVisible>
|
<div ref={ref}>
|
||||||
{visible => (
|
<BarChart
|
||||||
<BarChart
|
{...props}
|
||||||
{...props}
|
className={className}
|
||||||
className={className}
|
chartId={websiteId}
|
||||||
chartId={websiteId}
|
datasets={[
|
||||||
datasets={[
|
{
|
||||||
{
|
label: formatMessage(labels.uniqueVisitors),
|
||||||
label: intl.formatMessage({
|
data: data.sessions,
|
||||||
id: 'metrics.unique-visitors',
|
lineTension: 0,
|
||||||
defaultMessage: 'Unique visitors',
|
backgroundColor: colors.visitors.background,
|
||||||
}),
|
borderColor: colors.visitors.border,
|
||||||
data: data.sessions,
|
borderWidth: 1,
|
||||||
lineTension: 0,
|
},
|
||||||
backgroundColor: colors.visitors.background,
|
{
|
||||||
borderColor: colors.visitors.border,
|
label: formatMessage(labels.pageViews),
|
||||||
borderWidth: 1,
|
data: data.pageviews,
|
||||||
},
|
lineTension: 0,
|
||||||
{
|
backgroundColor: colors.views.background,
|
||||||
label: intl.formatMessage({
|
borderColor: colors.views.border,
|
||||||
id: 'metrics.page-views',
|
borderWidth: 1,
|
||||||
defaultMessage: 'Page views',
|
},
|
||||||
}),
|
]}
|
||||||
data: data.pageviews,
|
unit={unit}
|
||||||
lineTension: 0,
|
records={records}
|
||||||
backgroundColor: colors.views.background,
|
animationDuration={visible ? animationDuration : 0}
|
||||||
borderColor: colors.views.border,
|
onUpdate={handleUpdate}
|
||||||
borderWidth: 1,
|
loading={loading}
|
||||||
},
|
/>
|
||||||
]}
|
</div>
|
||||||
unit={unit}
|
|
||||||
records={records}
|
|
||||||
animationDuration={visible ? animationDuration : 0}
|
|
||||||
onUpdate={handleUpdate}
|
|
||||||
loading={loading}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CheckVisible>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import PageviewsChart from './PageviewsChart';
|
|||||||
import MetricsBar from './MetricsBar';
|
import MetricsBar from './MetricsBar';
|
||||||
import WebsiteHeader from './WebsiteHeader';
|
import WebsiteHeader from './WebsiteHeader';
|
||||||
import DateFilter from 'components/input/DateFilter';
|
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 ErrorMessage from 'components/common/ErrorMessage';
|
||||||
import FilterTags from 'components/metrics/FilterTags';
|
import FilterTags from 'components/metrics/FilterTags';
|
||||||
import RefreshButton from 'components/input/RefreshButton';
|
import RefreshButton from 'components/input/RefreshButton';
|
||||||
|
@ -6,7 +6,7 @@ import firstBy from 'thenby';
|
|||||||
import { GridRow, GridColumn } from 'components/layout/Grid';
|
import { GridRow, GridColumn } from 'components/layout/Grid';
|
||||||
import Page from 'components/layout/Page';
|
import Page from 'components/layout/Page';
|
||||||
import RealtimeChart from 'components/metrics/RealtimeChart';
|
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 PageHeader from 'components/layout/PageHeader';
|
||||||
import WorldMap from 'components/common/WorldMap';
|
import WorldMap from 'components/common/WorldMap';
|
||||||
import RealtimeLog from 'components/pages/realtime/RealtimeLog';
|
import RealtimeLog from 'components/pages/realtime/RealtimeLog';
|
||||||
|
@ -14,7 +14,7 @@ export default function DateRangeSetting() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flexbox width={400} gap={10}>
|
<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>
|
<Button onClick={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
);
|
);
|
||||||
|
@ -1,42 +1,38 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { parseISO } from 'date-fns';
|
import { parseISO } from 'date-fns';
|
||||||
import { getDateRange } from 'lib/date';
|
import { parseDateRange } from 'lib/date';
|
||||||
import { getItem, setItem } from 'next-basics';
|
import { setItem } from 'next-basics';
|
||||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants';
|
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||||
import useForceUpdate from './useForceUpdate';
|
|
||||||
import useLocale from './useLocale';
|
import useLocale from './useLocale';
|
||||||
import useStore, { setDateRange } from 'store/websites';
|
import { getWebsiteDateRange, setWebsiteDateRange } from 'store/websites';
|
||||||
|
import useStore, { setDateRange } from 'store/app';
|
||||||
|
|
||||||
|
function parseValue(value, locale) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return parseDateRange(value, locale);
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
return {
|
||||||
|
...value,
|
||||||
|
startDate: parseISO(value.startDate),
|
||||||
|
endDate: parseISO(value.endDate),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function useDateRange(websiteId) {
|
export default function useDateRange(websiteId) {
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const forceUpdate = useForceUpdate();
|
const websiteConfig = getWebsiteDateRange(websiteId);
|
||||||
const selector = useCallback(state => state?.[websiteId]?.dateRange, [websiteId]);
|
const defaultConfig = DEFAULT_DATE_RANGE;
|
||||||
const websiteDateRange = useStore(selector);
|
const globalConfig = useStore(state => state.dateRange);
|
||||||
const defaultDateRange = useMemo(() => getDateRange(DEFAULT_DATE_RANGE, locale), [locale]);
|
const dateRange = parseValue(websiteConfig || globalConfig || defaultConfig, locale);
|
||||||
|
|
||||||
const globalDefault = getItem(DATE_RANGE_CONFIG);
|
function saveDateRange(value) {
|
||||||
let globalDateRange;
|
|
||||||
|
|
||||||
if (globalDefault) {
|
|
||||||
if (typeof globalDefault === 'string') {
|
|
||||||
globalDateRange = getDateRange(globalDefault, locale);
|
|
||||||
} else if (typeof globalDefault === 'object') {
|
|
||||||
globalDateRange = {
|
|
||||||
...globalDefault,
|
|
||||||
startDate: parseISO(globalDefault.startDate),
|
|
||||||
endDate: parseISO(globalDefault.endDate),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveDateRange(dateRange) {
|
|
||||||
if (websiteId) {
|
if (websiteId) {
|
||||||
setDateRange(websiteId, dateRange);
|
setWebsiteDateRange(websiteId, value);
|
||||||
} else {
|
} else {
|
||||||
setItem(DATE_RANGE_CONFIG, dateRange);
|
setItem(DATE_RANGE_CONFIG, value);
|
||||||
forceUpdate();
|
setDateRange(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [websiteDateRange || globalDateRange || defaultDateRange, saveDateRange];
|
return [dateRange, saveDateRange];
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ export function getLocalTime(t) {
|
|||||||
return addMinutes(new Date(t), new Date().getTimezoneOffset());
|
return addMinutes(new Date(t), new Date().getTimezoneOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDateRange(value, locale = 'en-US') {
|
export function parseDateRange(value, locale = 'en-US') {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const dateLocale = getDateLocale(locale);
|
const dateLocale = getDateLocale(locale);
|
||||||
|
|
||||||
|
@ -44,7 +44,9 @@ export const useAuth = createMiddleware(async (req, res, next) => {
|
|||||||
user = await redis.get(authKey);
|
user = await redis.get(authKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
log({ token, payload, user, shareToken });
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
log({ token, shareToken, payload, user });
|
||||||
|
}
|
||||||
|
|
||||||
if (!user?.id && !shareToken) {
|
if (!user?.id && !shareToken) {
|
||||||
log('useAuth: User not authorized');
|
log('useAuth: User not authorized');
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
const pkg = require('./package.json');
|
const pkg = require('./package.json');
|
||||||
|
|
||||||
|
const CLOUD_URL = 'https://cloud.umami.is';
|
||||||
|
|
||||||
const contentSecurityPolicy = `
|
const contentSecurityPolicy = `
|
||||||
default-src 'self';
|
default-src 'self';
|
||||||
img-src *;
|
img-src *;
|
||||||
@ -33,11 +35,29 @@ if (process.env.FORCE_SSL) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
const rewrites = [];
|
||||||
|
|
||||||
|
if (process.env.COLLECT_API_ENDPOINT) {
|
||||||
|
rewrites.push({
|
||||||
|
source: 'process.env.COLLECT_API_ENDPOINT',
|
||||||
|
destination: '/api/in',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirects = [];
|
||||||
|
|
||||||
|
if (process.env.CLOUD_MODE) {
|
||||||
|
redirects.push({
|
||||||
|
source: '/login',
|
||||||
|
destination: CLOUD_URL,
|
||||||
|
permanent: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
env: {
|
env: {
|
||||||
currentVersion: pkg.version,
|
currentVersion: pkg.version,
|
||||||
isProduction: process.env.NODE_ENV === 'production',
|
isProduction: process.env.NODE_ENV === 'production',
|
||||||
uiDisabled: !!process.env.DISABLE_UI,
|
|
||||||
},
|
},
|
||||||
basePath: process.env.BASE_PATH,
|
basePath: process.env.BASE_PATH,
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
@ -66,10 +86,16 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
|
...rewrites,
|
||||||
{
|
{
|
||||||
source: '/telemetry.js',
|
source: '/telemetry.js',
|
||||||
destination: '/api/scripts/telemetry',
|
destination: '/api/scripts/telemetry',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
async redirects() {
|
||||||
|
return [...redirects];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"url": "https://github.com/umami-software/umami.git"
|
"url": "https://github.com/umami-software/umami.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev -p 3000",
|
||||||
"build": "npm-run-all build-db check-db build-tracker build-geo build-app",
|
"build": "npm-run-all build-db check-db build-tracker build-geo build-app",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"build-docker": "npm-run-all build-db build-tracker build-geo build-app",
|
"build-docker": "npm-run-all build-db build-tracker build-geo build-app",
|
||||||
@ -93,7 +93,7 @@
|
|||||||
"node-fetch": "^3.2.8",
|
"node-fetch": "^3.2.8",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-basics": "^0.68.0",
|
"react-basics": "^0.69.0",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-intl": "^5.24.7",
|
"react-intl": "^5.24.7",
|
||||||
|
@ -9,7 +9,7 @@ import 'styles/variables.css';
|
|||||||
import 'styles/locale.css';
|
import 'styles/locale.css';
|
||||||
import 'styles/index.css';
|
import 'styles/index.css';
|
||||||
import '@fontsource/inter/400.css';
|
import '@fontsource/inter/400.css';
|
||||||
import '@fontsource/inter/600.css';
|
import '@fontsource/inter/700.css';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
|
|
||||||
const client = new QueryClient({
|
const client = new QueryClient({
|
||||||
@ -24,11 +24,11 @@ const client = new QueryClient({
|
|||||||
export default function App({ Component, pageProps }) {
|
export default function App({ Component, pageProps }) {
|
||||||
const { locale, messages } = useLocale();
|
const { locale, messages } = useLocale();
|
||||||
const { basePath, pathname } = useRouter();
|
const { basePath, pathname } = useRouter();
|
||||||
useConfig();
|
const config = useConfig();
|
||||||
|
|
||||||
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
|
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
|
||||||
|
|
||||||
if (process.env.uiDisabled) {
|
if (!config || config.uiDisabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ export interface ConfigResponse {
|
|||||||
trackerScriptName: string;
|
trackerScriptName: string;
|
||||||
updatesDisabled: boolean;
|
updatesDisabled: boolean;
|
||||||
telemetryDisabled: boolean;
|
telemetryDisabled: boolean;
|
||||||
adminDisabled: boolean;
|
|
||||||
cloudMode: boolean;
|
cloudMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,8 +16,7 @@ export default async (req: NextApiRequest, res: NextApiResponse<ConfigResponse>)
|
|||||||
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
|
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
|
||||||
updatesDisabled: !!process.env.DISABLE_UPDATES,
|
updatesDisabled: !!process.env.DISABLE_UPDATES,
|
||||||
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
|
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
|
||||||
adminDisabled: !!process.env.DISABLE_ADMIN,
|
cloudMode: !!process.env.CLOUD_MODE,
|
||||||
cloudMode: process.env.CLOUD_MODE,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export default function LoginPage({ disabled }) {
|
|||||||
export async function getServerSideProps() {
|
export async function getServerSideProps() {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
disabled: !!(process.env.DISABLE_LOGIN || process.env.CLOUD_MODE),
|
disabled: !!process.env.DISABLE_LOGIN,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
export default () => null;
|
export default () => null;
|
||||||
|
|
||||||
export async function getServerSideProps() {
|
export async function getServerSideProps() {
|
||||||
const destination = process.env.CLOUD_MODE ? 'https://cloud.umami.is' : '/settings/websites';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
destination,
|
destination: '/settings/websites',
|
||||||
permanent: true,
|
permanent: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
14
store/app.js
14
store/app.js
@ -1,10 +1,18 @@
|
|||||||
import create from 'zustand';
|
import create from 'zustand';
|
||||||
import { DEFAULT_LOCALE, DEFAULT_THEME, LOCALE_CONFIG, THEME_CONFIG } from 'lib/constants';
|
import {
|
||||||
|
DATE_RANGE_CONFIG,
|
||||||
|
DEFAULT_DATE_RANGE,
|
||||||
|
DEFAULT_LOCALE,
|
||||||
|
DEFAULT_THEME,
|
||||||
|
LOCALE_CONFIG,
|
||||||
|
THEME_CONFIG,
|
||||||
|
} from 'lib/constants';
|
||||||
import { getItem } from 'next-basics';
|
import { getItem } from 'next-basics';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE,
|
locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE,
|
||||||
theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
|
theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
|
||||||
|
dateRange: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE,
|
||||||
shareToken: null,
|
shareToken: null,
|
||||||
user: null,
|
user: null,
|
||||||
config: null,
|
config: null,
|
||||||
@ -32,4 +40,8 @@ export function setConfig(config) {
|
|||||||
store.setState({ config });
|
store.setState({ config });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setDateRange(dateRange) {
|
||||||
|
store.setState({ dateRange });
|
||||||
|
}
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import create from 'zustand';
|
import create from 'zustand';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import { getDateRange } from 'lib/date';
|
import { parseDateRange } from 'lib/date';
|
||||||
|
|
||||||
const store = create(() => ({}));
|
const store = create(() => ({}));
|
||||||
|
|
||||||
export function setDateRange(websiteId, value) {
|
export function getWebsiteDateRange(websiteId) {
|
||||||
|
return store.getState()?.[websiteId];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setWebsiteDateRange(websiteId, value) {
|
||||||
store.setState(
|
store.setState(
|
||||||
produce(state => {
|
produce(state => {
|
||||||
if (!state[websiteId]) {
|
if (!state[websiteId]) {
|
||||||
@ -16,7 +20,7 @@ export function setDateRange(websiteId, value) {
|
|||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
const { locale } = app.getState();
|
const { locale } = app.getState();
|
||||||
dateRange = getDateRange(value, locale);
|
dateRange = parseDateRange(value, locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
state[websiteId].dateRange = { ...dateRange, modified: Date.now() };
|
state[websiteId].dateRange = { ...dateRange, modified: Date.now() };
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
(dnt && doNotTrack()) ||
|
(dnt && doNotTrack()) ||
|
||||||
(domain && !domains.includes(hostname));
|
(domain && !domains.includes(hostname));
|
||||||
|
|
||||||
const tracker_delay_duration = 300;
|
const delayDuration = 300;
|
||||||
const _data = 'data-';
|
const _data = 'data-';
|
||||||
const _false = 'false';
|
const _false = 'false';
|
||||||
const attr = currentScript.getAttribute.bind(currentScript);
|
const attr = currentScript.getAttribute.bind(currentScript);
|
||||||
@ -192,7 +192,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentUrl !== currentRef) {
|
if (currentUrl !== currentRef) {
|
||||||
setTimeout(() => trackView(), tracker_delay_duration);
|
setTimeout(() => trackView(), delayDuration);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6635,10 +6635,10 @@ rc@^1.2.7:
|
|||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-json-comments "~2.0.1"
|
strip-json-comments "~2.0.1"
|
||||||
|
|
||||||
react-basics@^0.68.0:
|
react-basics@^0.69.0:
|
||||||
version "0.68.0"
|
version "0.69.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.68.0.tgz#aa65061de838811f00396c26c72ded2bc7f413ae"
|
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.69.0.tgz#7399c37cf383551af90a5cbb4cac57ac21e7a40b"
|
||||||
integrity sha512-3C7HyHXOPNetu75r2soEPLlIAKxIqbNt51gim9SN33HJowDdDw+ZQ6V1kzob3DbFgWvuy0bm4bn7XiXcMJSA2w==
|
integrity sha512-5PGQiKMHOGhpYni6x8QaZO1mnKKZr2h/ka11oGjrNt8VPUopLhGp4euTUpFjP1NSDAczxH6qQzKV/tDagZmTjw==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.3.1"
|
classnames "^2.3.1"
|
||||||
date-fns "^2.29.3"
|
date-fns "^2.29.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user