Merge branch 'dev' into feat/um-202-event-data-new
commit
f4b32bab43
|
@ -1,10 +1,9 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { Icon, Icons, Text } from 'react-basics';
|
||||
import { messages } from 'components/messages';
|
||||
import styles from './ErrorMessage.module.css';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function ErrorMessage() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, messages } = useMessages();
|
||||
|
||||
return (
|
||||
<div className={styles.error}>
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import { useMeasure } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import useSticky from 'hooks/useSticky';
|
||||
|
||||
export default function StickyHeader({
|
||||
className,
|
||||
stickyClassName,
|
||||
stickyStyle,
|
||||
enabled = true,
|
||||
scrollElement,
|
||||
children,
|
||||
}) {
|
||||
const { ref: scrollRef, isSticky } = useSticky({ scrollElement });
|
||||
const { ref: measureRef, dimensions } = useMeasure();
|
||||
const active = enabled && isSticky;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={measureRef}
|
||||
data-sticky={active}
|
||||
style={active ? { height: dimensions.height } : null}
|
||||
>
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className={classNames(className, { [stickyClassName]: active })}
|
||||
style={active ? { ...stickyStyle, width: dimensions.width } : null}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Button, Banner, Row, Column, Flexbox } from 'react-basics';
|
||||
import { Button, Row, Column } from 'react-basics';
|
||||
import { setItem } from 'next-basics';
|
||||
import useStore, { checkVersion } from 'store/version';
|
||||
import { REPO_URL, VERSION_CHECK } from 'lib/constants';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import styles from './UpdateNotice.module.css';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UpdateNotice() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { latest, checked, hasUpdate, releaseUrl } = useStore();
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { useState } from 'react';
|
||||
import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { endOfYear, isSameDay } from 'date-fns';
|
||||
import DatePickerForm from 'components/metrics/DatePickerForm';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { dateFormat, getDateRangeValues } from 'lib/date';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
function DateFilter({ websiteId, value, className }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get } = useApi();
|
||||
const [dateRange, setDateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate } = dateRange;
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { Icon, Button, PopupTrigger, Popup, Tooltip, Text } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Icon, Button, PopupTrigger, Popup, Text } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { languages } from 'lib/lang';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './LanguageButton.module.css';
|
||||
|
||||
export default function LanguageButton({ tooltipPosition = 'top', menuPosition = 'right' }) {
|
||||
const { formatMessage } = useIntl();
|
||||
export default function LanguageButton() {
|
||||
const { locale, saveLocale } = useLocale();
|
||||
const items = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
|
||||
|
||||
|
@ -18,14 +15,12 @@ export default function LanguageButton({ tooltipPosition = 'top', menuPosition =
|
|||
|
||||
return (
|
||||
<PopupTrigger>
|
||||
<Tooltip label={formatMessage(labels.language)} position={tooltipPosition}>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Globe />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Popup position={menuPosition} alignment="end">
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Globe />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popup position="bottom" alignment="end">
|
||||
<div className={styles.menu}>
|
||||
{items.map(({ value, label }) => {
|
||||
return (
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Button, Icon, Icons, Tooltip } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import { labels } from 'components/messages';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function LogoutButton({ tooltipPosition = 'top' }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
return (
|
||||
<Link href="/logout">
|
||||
<Tooltip label={formatMessage(labels.logout)} position={tooltipPosition}>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import styles from './ProfileButton.module.css';
|
||||
|
||||
export default function ProfileButton() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
const router = useRouter();
|
||||
|
||||
const handleSelect = key => {
|
||||
if (key === 'profile') {
|
||||
router.push('/settings/profile');
|
||||
}
|
||||
if (key === 'logout') {
|
||||
router.push('/logout');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PopupTrigger>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Profile />
|
||||
</Icon>
|
||||
<Icon size="sm">
|
||||
<Icons.ChevronDown />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popup position="bottom" alignment="end">
|
||||
<Menu variant="popup" onSelect={handleSelect} className={styles.menu}>
|
||||
<Item key="user" className={styles.item}>
|
||||
<Text>{user.username}</Text>
|
||||
</Item>
|
||||
<Item key="profile" className={styles.item} divider={true}>
|
||||
<Icon>
|
||||
<Icons.User />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.profile)}</Text>
|
||||
</Item>
|
||||
<Item key="logout" className={styles.item}>
|
||||
<Icon>
|
||||
<Icons.Logout />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.logout)}</Text>
|
||||
</Item>
|
||||
</Menu>
|
||||
</Popup>
|
||||
</PopupTrigger>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.menu {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
background: var(--base50);
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { LoadingButton, Icon, Tooltip } from 'react-basics';
|
||||
import { setWebsiteDateRange } from 'store/websites';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
function RefreshButton({ websiteId, isLoading }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
|
||||
function handleClick() {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { Button, Icon, Tooltip, PopupTrigger, Popup, Form, FormRow } from 'react-basics';
|
||||
import TimezoneSetting from 'components/pages/settings/profile/TimezoneSetting';
|
||||
import DateRangeSetting from 'components/pages/settings/profile/DateRangeSetting';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './SettingsButton.module.css';
|
||||
|
||||
export default function SettingsButton() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PopupTrigger>
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { useTransition, animated } from 'react-spring';
|
||||
import { Button, Icon, Tooltip } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './ThemeButton.module.css';
|
||||
|
||||
export default function ThemeButton({ tooltipPosition = 'top' }) {
|
||||
export default function ThemeButton() {
|
||||
const [theme, setTheme] = useTheme();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const transitions = useTransition(theme, {
|
||||
initial: { opacity: 1 },
|
||||
|
@ -28,14 +25,12 @@ export default function ThemeButton({ tooltipPosition = 'top' }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={formatMessage(labels.theme)} position={tooltipPosition}>
|
||||
<Button variant="quiet" className={styles.button} onClick={handleClick}>
|
||||
{transitions((style, item) => (
|
||||
<animated.div key={item} style={style}>
|
||||
<Icon className={styles.icon}>{item === 'light' ? <Icons.Sun /> : <Icons.Moon />}</Icon>
|
||||
</animated.div>
|
||||
))}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button variant="quiet" className={styles.button} onClick={handleClick}>
|
||||
{transitions((style, item) => (
|
||||
<animated.div key={item} style={style}>
|
||||
<Icon className={styles.icon}>{item === 'light' ? <Icons.Sun /> : <Icons.Moon />}</Icon>
|
||||
</animated.div>
|
||||
))}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { Dropdown, Item } from 'react-basics';
|
||||
import { labels } from 'components/messages';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsiteSelect({ websiteId, onSelect }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data } = useQuery(['websites:me'], () => get('/me/websites'));
|
||||
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
.layout {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: max-content 1fr;
|
||||
grid-template-rows: max-content 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.nav {
|
||||
grid-row: 1 / 3;
|
||||
height: 60px;
|
||||
width: 100vw;
|
||||
z-index: 100;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-area: 1 / 2;
|
||||
overflow: auto;
|
||||
height: 100vh;
|
||||
grid-column: 1;
|
||||
grid-row: 2 / 3;
|
||||
min-height: 0;
|
||||
max-height: calc(100vh - 60px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
|
@ -1,69 +1,50 @@
|
|||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Icon, Text } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import Icons from 'components/icons';
|
||||
import ThemeButton from 'components/input/ThemeButton';
|
||||
import LanguageButton from 'components/input/LanguageButton';
|
||||
import LogoutButton from 'components/input/LogoutButton';
|
||||
import { labels } from 'components/messages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import NavGroup from './NavGroup';
|
||||
import ProfileButton from 'components/input/ProfileButton';
|
||||
import styles from './NavBar.module.css';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function NavBar() {
|
||||
const { user } = useUser();
|
||||
const { cloudMode } = useConfig();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [minimized, setMinimized] = useState(false);
|
||||
const tooltipPosition = minimized ? 'right' : 'top';
|
||||
|
||||
const analytics = [
|
||||
const links = [
|
||||
{ label: formatMessage(labels.dashboard), url: '/dashboard', icon: <Icons.Dashboard /> },
|
||||
{ label: formatMessage(labels.realtime), url: '/realtime', icon: <Icons.Clock /> },
|
||||
];
|
||||
|
||||
const settings = [
|
||||
!cloudMode && {
|
||||
label: formatMessage(labels.websites),
|
||||
url: '/settings/websites',
|
||||
icon: <Icons.Globe />,
|
||||
},
|
||||
user?.isAdmin && {
|
||||
label: formatMessage(labels.users),
|
||||
url: '/settings/users',
|
||||
icon: <Icons.User />,
|
||||
},
|
||||
!cloudMode && {
|
||||
label: formatMessage(labels.teams),
|
||||
url: '/settings/teams',
|
||||
icon: <Icons.Users />,
|
||||
},
|
||||
{ label: formatMessage(labels.profile), url: '/settings/profile', icon: <Icons.Profile /> },
|
||||
!cloudMode && { label: formatMessage(labels.settings), url: '/settings', icon: <Icons.Gear /> },
|
||||
].filter(n => n);
|
||||
|
||||
const handleMinimize = () => setMinimized(state => !state);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.navbar, { [styles.minimized]: minimized })}>
|
||||
<div className={styles.header} onClick={handleMinimize}>
|
||||
<div className={styles.logo} onClick={handleMinimize}>
|
||||
<Icon size="lg">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text className={styles.text}>umami</Text>
|
||||
<Icon size="sm" rotate={minimized ? -90 : 90} className={styles.icon}>
|
||||
<Icons.ChevronDown />
|
||||
</Icon>
|
||||
</div>
|
||||
<NavGroup title={formatMessage(labels.analytics)} items={analytics} minimized={minimized} />
|
||||
<NavGroup title={formatMessage(labels.settings)} items={settings} minimized={minimized} />
|
||||
<div className={styles.footer}>
|
||||
<div className={styles.buttons}>
|
||||
<ThemeButton tooltipPosition={tooltipPosition} />
|
||||
<LanguageButton tooltipPosition={tooltipPosition} />
|
||||
{!cloudMode && <LogoutButton tooltipPosition={tooltipPosition} />}
|
||||
</div>
|
||||
<div className={styles.links}>
|
||||
{links.map(({ url, icon, label }) => {
|
||||
return (
|
||||
<Link key={url} href={url}>
|
||||
<Icon>{icon}</Icon>
|
||||
<Text>{label}</Text>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
{!cloudMode && <ProfileButton />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,62 +1,49 @@
|
|||
.navbar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
background: var(--base75);
|
||||
height: 100%;
|
||||
width: 200px;
|
||||
border-right: 2px solid var(--base200);
|
||||
border-bottom: 1px solid var(--base200);
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
padding: 20px 0;
|
||||
cursor: pointer;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header:hover .icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.icon {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
border-radius: 100%;
|
||||
color: var(--base50);
|
||||
background: var(--base800);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.minimized.navbar {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.minimized .text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: 1;
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 20px;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
padding: 0 40px;
|
||||
flex: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
.links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
color: var(--font-color100);
|
||||
}
|
||||
|
||||
.minimized .buttons {
|
||||
flex-direction: column;
|
||||
.links a:hover {
|
||||
color: var(--primary400);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import classNames from 'classnames';
|
||||
import styles from './Page.module.css';
|
||||
import { Banner, Loading } from 'react-basics';
|
||||
import styles from './Page.module.css';
|
||||
|
||||
export default function Page({ className, error, loading, children }) {
|
||||
if (error) {
|
||||
|
|
|
@ -109,6 +109,8 @@ export const labels = defineMessages({
|
|||
laptop: { id: 'label.laptop', defaultMessage: 'Laptop' },
|
||||
tablet: { id: 'label.tablet', defaultMessage: 'Tablet' },
|
||||
mobile: { id: 'label.mobile', defaultMessage: 'Mobile' },
|
||||
toggleCharts: { id: 'label.toggle-charts', defaultMessage: 'Toggle charts' },
|
||||
editDashboard: { id: 'label.edit-dashboard', defaultMessage: 'Edit dashboard' },
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
|
@ -201,9 +203,3 @@ export const messages = defineMessages({
|
|||
defaultMessage: '{event} on {url}',
|
||||
},
|
||||
});
|
||||
|
||||
export function getMessage(id, formatMessage) {
|
||||
const message = Object.values(messages).find(value => value.id === id);
|
||||
|
||||
return message ? formatMessage(message) : id;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { useMemo } from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './ActiveUsers.module.css';
|
||||
|
||||
export default function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, messages } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data } = useQuery(
|
||||
['websites:active', websiteId],
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
import MetricsTable from './MetricsTable';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
|
||||
const messages = defineMessages({
|
||||
unknown: { id: 'label.unknown', defaultMessage: 'Unknown' },
|
||||
countries: { id: 'metrics.countries', defaultMessage: 'Countries' },
|
||||
visitors: { id: 'metrics.visitors', defaultMessage: 'Visitors' },
|
||||
});
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
function renderLink({ x: code }) {
|
||||
return (
|
||||
|
@ -22,7 +16,7 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
|||
<FilterLink
|
||||
id="country"
|
||||
value={code}
|
||||
label={countryNames[code] ?? formatMessage(messages.unknown)}
|
||||
label={countryNames[code] ?? formatMessage(labels.unknown)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -31,9 +25,9 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
|
|||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={formatMessage(messages.countries)}
|
||||
title={formatMessage(labels.countries)}
|
||||
type="country"
|
||||
metric={formatMessage(messages.visitors)}
|
||||
metric={formatMessage(labels.visitors)}
|
||||
websiteId={websiteId}
|
||||
onDataLoad={data => onDataLoad?.(percentFilter(data))}
|
||||
renderLabel={renderLink}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, ButtonGroup, Calendar } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { isAfter, isBefore, isSameDay } from 'date-fns';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { getDateRangeValues } from 'lib/date';
|
||||
import { getDateLocale } from 'lib/lang';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './DatePickerForm.module.css';
|
||||
import { FILTER_DAY, FILTER_RANGE } from 'lib/constants';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './DatePickerForm.module.css';
|
||||
|
||||
export default function DatePickerForm({
|
||||
startDate: defaultStartDate,
|
||||
|
@ -24,7 +23,7 @@ export default function DatePickerForm({
|
|||
const [startDate, setStartDate] = useState(defaultStartDate);
|
||||
const [endDate, setEndDate] = useState(defaultEndDate);
|
||||
const { locale } = useLocale();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const disabled =
|
||||
selected === FILTER_DAY
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import MetricsTable from './MetricsTable';
|
||||
import { useIntl } from 'react-intl';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function DevicesTable({ websiteId, ...props }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
function renderLink({ x: device }) {
|
||||
return (
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
|
||||
const messages = defineMessages({
|
||||
events: { id: 'metrics.events', defaultMessage: 'Events' },
|
||||
actions: { id: 'metrics.actions', defaultMessage: 'Actions' },
|
||||
});
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function EventsTable({ websiteId, ...props }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
function handleDataLoad(data) {
|
||||
props.onDataLoad?.(data);
|
||||
|
@ -16,9 +11,9 @@ export default function EventsTable({ websiteId, ...props }) {
|
|||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={formatMessage(messages.events)}
|
||||
title={formatMessage(labels.events)}
|
||||
type="event"
|
||||
metric={formatMessage(messages.actions)}
|
||||
metric={formatMessage(labels.actions)}
|
||||
websiteId={websiteId}
|
||||
onDataLoad={handleDataLoad}
|
||||
/>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import { Button, Icon, Icons, Text } from 'react-basics';
|
||||
import { labels } from 'components/messages';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import styles from './FilterTags.module.css';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function FilterTags({ websiteId, params, onClick }) {
|
||||
const { formatMessage } = useIntl();
|
||||
export default function FilterTags({ websiteId, params }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
router,
|
||||
resolveUrl,
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { useState } from 'react';
|
||||
import { Loading } from 'react-basics';
|
||||
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';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function MetricsBar({ websiteId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, modified } = dateRange;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Loading, Icon, Text, Button } from 'react-basics';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import Link from 'next/link';
|
||||
import firstBy from 'thenby';
|
||||
import classNames from 'classnames';
|
||||
|
@ -12,12 +11,9 @@ import ErrorMessage from 'components/common/ErrorMessage';
|
|||
import DataTable from './DataTable';
|
||||
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './MetricsTable.module.css';
|
||||
|
||||
const messages = defineMessages({
|
||||
more: { id: 'label.more', defaultMessage: 'More' },
|
||||
});
|
||||
|
||||
export default function MetricsTable({
|
||||
websiteId,
|
||||
type,
|
||||
|
@ -35,7 +31,7 @@ export default function MetricsTable({
|
|||
router,
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
const { data, isLoading, isFetched, error } = useQuery(
|
||||
|
@ -81,7 +77,7 @@ export default function MetricsTable({
|
|||
{data && !error && limit && (
|
||||
<Link href={router.pathname} as={resolveUrl({ view: type })}>
|
||||
<Button variant="quiet">
|
||||
<Text>{formatMessage(messages.more)}</Text>
|
||||
<Text>{formatMessage(labels.more)}</Text>
|
||||
<Icon size="sm">
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function OSTable({ websiteId, ...props }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
function renderLink({ x: os }) {
|
||||
return <FilterLink id="os" value={os} />;
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useState } from 'react';
|
||||
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';
|
||||
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const filters = {
|
||||
[FILTER_RAW]: null,
|
||||
|
@ -14,7 +13,7 @@ const filters = {
|
|||
|
||||
export default function PagesTable({ websiteId, showFilters, ...props }) {
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useVisible } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { colord } from 'colord';
|
||||
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';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function PageviewsChart({
|
||||
websiteId,
|
||||
|
@ -17,7 +16,7 @@ export default function PageviewsChart({
|
|||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||
...props
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [theme] = useTheme();
|
||||
const { ref, visible } = useVisible();
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import Tag from 'components/common/Tag';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import { paramFilter } from 'lib/filters';
|
||||
import { FILTER_RAW, FILTER_COMBINED } from 'lib/constants';
|
||||
import { labels } from 'components/messages';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const filters = {
|
||||
[FILTER_RAW]: null,
|
||||
|
@ -15,7 +14,7 @@ const filters = {
|
|||
|
||||
export default function QueryParametersTable({ websiteId, showFilters, ...props }) {
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function ReferrersTable({ websiteId, ...props }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const renderLink = ({ w: link, x: referrer }) => {
|
||||
return referrer ? (
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Button, Icon, Text, Row, Column } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import MetricsBar from './MetricsBar';
|
||||
import WebsiteHeader from './WebsiteHeader';
|
||||
import DateFilter from 'components/input/DateFilter';
|
||||
import StickyHeader from 'components/common/StickyHeader';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import FilterTags from 'components/metrics/FilterTags';
|
||||
import RefreshButton from 'components/input/RefreshButton';
|
||||
|
@ -16,9 +15,9 @@ import useTimezone from 'hooks/useTimezone';
|
|||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { getDateArray, getDateLength } from 'lib/date';
|
||||
import Icons from 'components/icons';
|
||||
import { UI_LAYOUT_BODY } from 'lib/constants';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
import useSticky from 'hooks/useSticky';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsiteChart({
|
||||
websiteId,
|
||||
|
@ -29,7 +28,7 @@ export default function WebsiteChart({
|
|||
showDetailsButton = false,
|
||||
onDataLoad = () => {},
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, unit, value, modified } = dateRange;
|
||||
const [timezone] = useTimezone();
|
||||
|
@ -37,6 +36,7 @@ export default function WebsiteChart({
|
|||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
const { get, useQuery } = useApi();
|
||||
const { ref, isSticky } = useSticky({ enabled: stickyHeader });
|
||||
|
||||
const { data, isLoading, error } = useQuery(
|
||||
['websites:pageviews', websiteId, modified, url, referrer, os, browser, device, country],
|
||||
|
@ -81,21 +81,21 @@ export default function WebsiteChart({
|
|||
)}
|
||||
</WebsiteHeader>
|
||||
<FilterTags websiteId={websiteId} params={{ url, referrer, os, browser, device, country }} />
|
||||
<StickyHeader
|
||||
stickyClassName={styles.sticky}
|
||||
enabled={stickyHeader}
|
||||
scrollElement={document.getElementById(UI_LAYOUT_BODY) || document}
|
||||
<Row
|
||||
ref={ref}
|
||||
className={classNames(styles.header, {
|
||||
[styles.sticky]: stickyHeader,
|
||||
[styles.isSticky]: isSticky,
|
||||
})}
|
||||
>
|
||||
<Row className={styles.header}>
|
||||
<Column>
|
||||
<MetricsBar websiteId={websiteId} />
|
||||
</Column>
|
||||
<Column className={styles.actions}>
|
||||
<RefreshButton websiteId={websiteId} isLoading={isLoading} />
|
||||
<DateFilter websiteId={websiteId} value={value} className={styles.dropdown} />
|
||||
</Column>
|
||||
</Row>
|
||||
</StickyHeader>
|
||||
<Column>
|
||||
<MetricsBar websiteId={websiteId} />
|
||||
</Column>
|
||||
<Column className={styles.actions}>
|
||||
<RefreshButton websiteId={websiteId} isLoading={isLoading} />
|
||||
<DateFilter websiteId={websiteId} value={value} className={styles.dropdown} />
|
||||
</Column>
|
||||
</Row>
|
||||
<Row>
|
||||
<Column className={styles.chart}>
|
||||
{error && <ErrorMessage />}
|
||||
|
|
|
@ -17,22 +17,23 @@
|
|||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
min-height: 90px;
|
||||
margin-bottom: 20px;
|
||||
background: var(--base50);
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
background: var(--base50);
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.isSticky {
|
||||
border-bottom: 1px solid var(--base300);
|
||||
z-index: 3;
|
||||
width: inherit;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, Icon, Icons, Text, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Link from 'next/link';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
|
@ -9,16 +8,16 @@ import DashboardSettingsButton from 'components/pages/dashboard/DashboardSetting
|
|||
import DashboardEdit from 'components/pages/dashboard/DashboardEdit';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useDashboard from 'store/dashboard';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function Dashboard({ userId }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const dashboard = useDashboard();
|
||||
const { showCharts, limit, editing } = dashboard;
|
||||
const [max, setMax] = useState(limit);
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading, error } = useQuery(['websites'], () => get('/websites', { userId }));
|
||||
const { formatMessage } = useIntl();
|
||||
const hasData = data && data.length !== 0;
|
||||
|
||||
function handleMore() {
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import classNames from 'classnames';
|
||||
import { Button } from 'react-basics';
|
||||
import { firstBy } from 'thenby';
|
||||
import useDashboard, { saveDashboard } from 'store/dashboard';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './DashboardEdit.module.css';
|
||||
|
||||
const messages = defineMessages({
|
||||
save: { id: 'label.save', defaultMessage: 'Save' },
|
||||
reset: { id: 'label.reset', defaultMessage: 'Reset' },
|
||||
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
const dragId = 'dashboard-website-ordering';
|
||||
|
||||
export default function DashboardEdit({ websites }) {
|
||||
const settings = useDashboard();
|
||||
const { websiteOrder } = settings;
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [order, setOrder] = useState(websiteOrder || []);
|
||||
|
||||
const ordered = useMemo(
|
||||
|
@ -58,13 +52,13 @@ export default function DashboardEdit({ websites }) {
|
|||
<>
|
||||
<div className={styles.buttons}>
|
||||
<Button onClick={handleSave} variant="action" size="small">
|
||||
{formatMessage(messages.save)}
|
||||
{formatMessage(labels.save)}
|
||||
</Button>
|
||||
<Button onClick={handleCancel} size="small">
|
||||
{formatMessage(messages.cancel)}
|
||||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
<Button onClick={handleReset} size="small">
|
||||
{formatMessage(messages.reset)}
|
||||
{formatMessage(labels.reset)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.dragActive}>
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
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';
|
||||
|
||||
const messages = defineMessages({
|
||||
toggleCharts: { id: 'message.toggle-charts', defaultMessage: 'Toggle charts' },
|
||||
editDashboard: { id: 'message.edit-dashboard', defaultMessage: 'Edit dashboard' },
|
||||
});
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function DashboardSettingsButton() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const menuOptions = [
|
||||
{
|
||||
label: formatMessage(messages.toggleCharts),
|
||||
label: formatMessage(labels.toggleCharts),
|
||||
value: 'charts',
|
||||
},
|
||||
{
|
||||
label: formatMessage(messages.editDashboard),
|
||||
label: formatMessage(labels.editDashboard),
|
||||
value: 'order',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { labels } from 'components/messages';
|
||||
import DataTable from 'components/metrics/DataTable';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function RealtimeCountries({ data }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { subMinutes, startOfMinute } from 'date-fns';
|
||||
import { useRouter } from 'next/router';
|
||||
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/common/StickyHeader';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import WorldMap from 'components/common/WorldMap';
|
||||
import RealtimeLog from 'components/pages/realtime/RealtimeLog';
|
||||
|
@ -15,8 +13,8 @@ import RealtimeUrls from 'components/pages/realtime/RealtimeUrls';
|
|||
import RealtimeCountries from 'components/pages/realtime/RealtimeCountries';
|
||||
import WebsiteSelect from 'components/input/WebsiteSelect';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { labels } from 'components/messages';
|
||||
import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants';
|
||||
import styles from './RealtimeDashboard.module.css';
|
||||
|
||||
|
@ -28,7 +26,7 @@ function mergeData(state = [], data = [], time) {
|
|||
}
|
||||
|
||||
export default function RealtimeDashboard({ websiteId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const router = useRouter();
|
||||
const [currentData, setCurrentData] = useState();
|
||||
const { get, useQuery } = useApi();
|
||||
|
@ -104,9 +102,7 @@ export default function RealtimeDashboard({ websiteId }) {
|
|||
<PageHeader title={formatMessage(labels.realtime)}>
|
||||
<WebsiteSelect websiteId={websiteId} onSelect={handleSelect} />
|
||||
</PageHeader>
|
||||
<StickyHeader stickyClassName={styles.sticky}>
|
||||
<RealtimeHeader websiteId={websiteId} data={currentData} />
|
||||
</StickyHeader>
|
||||
<RealtimeHeader websiteId={websiteId} data={currentData} />
|
||||
<div className={styles.chart}>
|
||||
<RealtimeChart data={realtimeData} unit="minute" records={REALTIME_RANGE} />
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import MetricCard from 'components/metrics/MetricCard';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './RealtimeHeader.module.css';
|
||||
|
||||
export default function RealtimeHeader({ data = {} }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { pageviews, visitors, events, countries } = data;
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function RealtimeHome() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
const router = useRouter();
|
||||
const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites'));
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { StatusLight, Icon, Text } from 'react-basics';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import firstBy from 'thenby';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import NoData from 'components/common/NoData';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import { BROWSERS } from 'lib/constants';
|
||||
|
@ -14,6 +12,7 @@ import { dateFormat } from 'lib/date';
|
|||
import { safeDecodeURI } from 'next-basics';
|
||||
import Icons from 'components/icons';
|
||||
import styles from './RealtimeLog.module.css';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const TYPE_ALL = 'all';
|
||||
const TYPE_PAGEVIEW = 'pageview';
|
||||
|
@ -27,7 +26,7 @@ const icons = {
|
|||
};
|
||||
|
||||
export default function RealtimeLog({ data, websiteDomain }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||
const { locale } = useLocale();
|
||||
const countryNames = useCountryNames(locale);
|
||||
const [filter, setFilter] = useState(TYPE_ALL);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { ButtonGroup, Button, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import firstBy from 'thenby';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import DataTable from 'components/metrics/DataTable';
|
||||
import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function RealtimeUrls({ websiteDomain, data = {} }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { pageviews } = data;
|
||||
const [filter, setFilter] = useState(FILTER_REFERRERS);
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import DateFilter from 'components/input/DateFilter';
|
||||
import { Button, Flexbox } from 'react-basics';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function DateRangeSetting() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [dateRange, setDateRange] = useDateRange();
|
||||
const { startDate, endDate, value } = dateRange;
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { Button, Dropdown, Item, Flexbox } from 'react-basics';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { DEFAULT_LOCALE } from 'lib/constants';
|
||||
import { languages } from 'lib/lang';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function LanguageSetting() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { locale, saveLocale } = useLocale();
|
||||
const options = Object.keys(languages);
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { Button, Icon, Text, useToast, ModalTrigger, Modal } from 'react-basics';
|
||||
import PasswordEditForm from 'components/pages/settings/profile/PasswordEditForm';
|
||||
import Icons from 'components/icons';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function PasswordChangeButton() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { toast, showToast } = useToast();
|
||||
|
||||
const handleSave = () => {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useRef } from 'react';
|
||||
import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function PasswordEditForm({ onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => post('/me/password', data));
|
||||
const ref = useRef(null);
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { Form, FormRow } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import TimezoneSetting from 'components/pages/settings/profile/TimezoneSetting';
|
||||
import DateRangeSetting from 'components/pages/settings/profile/DateRangeSetting';
|
||||
import LanguageSetting from 'components/pages/settings/profile/LanguageSetting';
|
||||
import ThemeSetting from 'components/pages/settings/profile/ThemeSetting';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function ProfileDetails() {
|
||||
const { user } = useUser();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
|
@ -20,7 +19,9 @@ export default function ProfileDetails() {
|
|||
return (
|
||||
<Form>
|
||||
<FormRow label={formatMessage(labels.username)}>{username}</FormRow>
|
||||
<FormRow label={formatMessage(labels.role)}>{role}</FormRow>
|
||||
<FormRow label={formatMessage(labels.role)}>
|
||||
{formatMessage(labels[role] || labels.unknown)}
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.defaultDateRange)}>
|
||||
<DateRangeSetting />
|
||||
</FormRow>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import ProfileDetails from './ProfileDetails';
|
||||
import PasswordChangeButton from './PasswordChangeButton';
|
||||
import { labels } from 'components/messages';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function ProfileSettings() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { cloudMode } = useConfig();
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { Dropdown, Item, Button, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { listTimeZones } from 'timezone-support';
|
||||
import useTimezone from 'hooks/useTimezone';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import { getTimezone } from 'lib/date';
|
||||
import { labels } from 'components/messages';
|
||||
|
||||
export default function TimezoneSetting() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [timezone, saveTimezone] = useTimezone();
|
||||
const options = listTimeZones();
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useRef } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
|
@ -10,10 +9,10 @@ import {
|
|||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, getMessage } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamJoinForm({ onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, getMessage } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post('/teams/join', data));
|
||||
const ref = useRef(null);
|
||||
|
@ -28,7 +27,7 @@ export default function TeamJoinForm({ onSave, onClose }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error && getMessage(error, formatMessage)}>
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error && getMessage(error)}>
|
||||
<FormRow label={formatMessage(labels.accessCode)}>
|
||||
<FormInput name="accessCode" rules={{ required: formatMessage(labels.required) }}>
|
||||
<TextField autoComplete="off" />
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useRef } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
|
@ -10,10 +9,10 @@ import {
|
|||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamAddForm({ onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => post('/teams', data));
|
||||
const ref = useRef(null);
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamDeleteForm({ teamId, teamName, onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => del(`/teams/${teamId}`, data));
|
||||
|
||||
|
|
|
@ -8,16 +8,15 @@ import {
|
|||
Button,
|
||||
Flexbox,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { getRandomChars } from 'next-basics';
|
||||
import { useRef, useState } from 'react';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const generateId = () => getRandomChars(16);
|
||||
|
||||
export default function TeamEditForm({ teamId, data, onSave, readOnly }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data));
|
||||
const ref = useRef(null);
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { messages } from 'components/messages';
|
||||
import { Loading, useToast } from 'react-basics';
|
||||
import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { Loading, useToast } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamMembers({ teamId, readOnly }) {
|
||||
const { toast, showToast } = useToast();
|
||||
const { get, useQuery } = useApi();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { data, isLoading, refetch } = useQuery(['teams:users', teamId], () =>
|
||||
get(`/teams/${teamId}/users`),
|
||||
);
|
||||
|
@ -18,7 +17,7 @@ export default function TeamMembers({ teamId, readOnly }) {
|
|||
|
||||
const handleSave = async () => {
|
||||
await refetch();
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
showToast({ message: formatMessage(labels.saved), variant: 'success' });
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -11,14 +11,13 @@ import {
|
|||
Flexbox,
|
||||
Text,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import { labels } from 'components/messages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamMembersTable({ data = [], onSave, readOnly }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate } = useMutation(data => del(`/teamUsers/${data.teamUserId}`));
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import useUser from 'hooks/useUser';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import TeamEditForm from './TeamEditForm';
|
||||
import TeamMembers from './TeamMembers';
|
||||
import TeamWebsites from './TeamWebsites';
|
||||
|
||||
export default function TeamSettings({ teamId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { user } = useUser();
|
||||
const [values, setValues] = useState(null);
|
||||
const [tab, setTab] = useState('details');
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable';
|
||||
import useApi from 'hooks/useApi';
|
||||
import {
|
||||
ActionForm,
|
||||
Button,
|
||||
|
@ -13,12 +9,15 @@ import {
|
|||
Text,
|
||||
useToast,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable';
|
||||
import WebsiteAddTeamForm from 'components/pages/settings/teams/WebsiteAddTeamForm';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamWebsites({ teamId }) {
|
||||
const { toast, showToast } = useToast();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading, refetch } = useQuery(['teams:websites', teamId], () =>
|
||||
get(`/teams/${teamId}/websites`),
|
||||
|
|
|
@ -12,13 +12,12 @@ import {
|
|||
Icons,
|
||||
Flexbox,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { labels } from 'components/messages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamWebsitesTable({ data = [], onSave }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate } = useMutation(({ teamWebsiteId }) => del(`/teamWebsites/${teamWebsiteId}`));
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, Icon, Modal, ModalTrigger, useToast, Text, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import TeamAddForm from 'components/pages/settings/teams/TeamAddForm';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import TeamsTable from 'components/pages/settings/teams/TeamsTable';
|
||||
import Page from 'components/layout/Page';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import Icons from 'components/icons';
|
||||
import TeamJoinForm from './JoinTeamForm';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamsList() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const [update, setUpdate] = useState(0);
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading, error } = useQuery(['teams', update], () => get(`/teams`));
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { labels } from 'components/messages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Button,
|
||||
|
@ -17,11 +14,13 @@ import {
|
|||
TableRow,
|
||||
Text,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import TeamDeleteForm from './TeamDeleteForm';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { ROLES } from 'lib/constants';
|
||||
|
||||
export default function TeamsTable({ data = [], onDelete }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
const columns = [
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { labels } from 'components/messages';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button, Dropdown, Form, FormButtons, FormRow, Item, SubmitButton } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import WebsiteTags from './WebsiteTags';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsiteAddTeamForm({ teamId, onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get, post, useQuery, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
||||
const { data: websites } = useQuery(['websites'], () => get('/websites'));
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { Button, Icon, Text, Modal, Icons, ModalTrigger } from 'react-basics';
|
||||
import UserAddForm from './UserAddForm';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UserAddButton({ onSave }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleSave = () => {
|
||||
onSave();
|
||||
|
|
|
@ -10,15 +10,14 @@ import {
|
|||
SubmitButton,
|
||||
Button,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UserAddForm({ onSave, onClose }) {
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => post(`/users`, data));
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleSubmit = async data => {
|
||||
mutate(data, {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useMutation } from '@tanstack/react-query';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UserDeleteForm({ userId, username, onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, FormattedMessage, labels, messages } = useMessages();
|
||||
const { del } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(() => del(`/users/${userId}`));
|
||||
|
||||
|
|
|
@ -9,13 +9,12 @@ import {
|
|||
SubmitButton,
|
||||
PasswordField,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UserEditForm({ userId, data, onSave }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(({ username }) => post(`/users/${userId}`, { username }));
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import UserEditForm from 'components/pages/settings/users//UserEditForm';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import UserWebsites from './UserWebsites';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UserSettings({ userId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const [edit, setEdit] = useState(false);
|
||||
const [values, setValues] = useState(null);
|
||||
const [tab, setTab] = useState('details');
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Loading } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import WebsitesTable from 'components/pages/settings/websites/WebsitesTable';
|
||||
import { messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UserWebsites({ userId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, messages } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading } = useQuery(['user:websites', userId], () =>
|
||||
get(`/users/${userId}/websites`),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
import { useToast } from 'react-basics';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
|
@ -6,11 +6,10 @@ import UsersTable from './UsersTable';
|
|||
import UserAddButton from './UserAddButton';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { useToast } from 'react-basics';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UsersList() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { user } = useUser();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading, error, refetch } = useQuery(['user'], () => get(`/users`), {
|
||||
|
|
|
@ -13,16 +13,15 @@ import {
|
|||
ModalTrigger,
|
||||
Modal,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { formatDistance } from 'date-fns';
|
||||
import Link from 'next/link';
|
||||
import useUser from 'hooks/useUser';
|
||||
import UserDeleteForm from './UserDeleteForm';
|
||||
import { labels } from 'components/messages';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function UsersTable({ data = [], onDelete }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
const columns = [
|
||||
|
|
|
@ -8,16 +8,15 @@ import {
|
|||
Button,
|
||||
Toggle,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { getRandomChars } from 'next-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const generateId = () => getRandomChars(16);
|
||||
|
||||
export default function ShareUrl({ websiteId, data, onSave }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { name, shareId } = data;
|
||||
const [id, setId] = useState(shareId);
|
||||
const { post, useMutation } = useApi();
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { TextArea } from 'react-basics';
|
||||
import { TRACKER_SCRIPT_URL } from 'lib/constants';
|
||||
import { messages } from 'components/messages';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TrackingCode({ websiteId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, messages } = useMessages();
|
||||
const url = TRACKER_SCRIPT_URL.startsWith('http')
|
||||
? TRACKER_SCRIPT_URL
|
||||
: `${location.origin}${TRACKER_SCRIPT_URL}`;
|
||||
|
|
|
@ -7,17 +7,12 @@ import {
|
|||
Button,
|
||||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { DOMAIN_REGEX } from 'lib/constants';
|
||||
import { labels } from 'components/messages';
|
||||
|
||||
const messages = defineMessages({
|
||||
invalidDomain: { id: 'label.invalid-domain', defaultMessage: 'Invalid domain' },
|
||||
});
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsiteAddForm({ onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => post('/websites', data));
|
||||
|
||||
|
@ -42,7 +37,7 @@ export default function WebsiteAddForm({ onSave, onClose }) {
|
|||
name="domain"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
pattern: { value: DOMAIN_REGEX, message: formatMessage(messages.invalidDomain) },
|
||||
pattern: { value: DOMAIN_REGEX, message: formatMessage(labels.invalidDomain) },
|
||||
}}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Button, Modal, ModalTrigger, ActionForm } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import WebsiteDeleteForm from 'components/pages/settings/websites/WebsiteDeleteForm';
|
||||
import WebsiteResetForm from 'components/pages/settings/websites/WebsiteResetForm';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsiteData({ websiteId, onSave }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
|
||||
const handleReset = async () => {
|
||||
onSave('reset');
|
||||
|
|
|
@ -7,14 +7,13 @@ import {
|
|||
SubmitButton,
|
||||
TextField,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const CONFIRM_VALUE = 'DELETE';
|
||||
|
||||
export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => del(`/websites/${websiteId}`, data));
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics';
|
||||
import { useRef } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { DOMAIN_REGEX } from 'lib/constants';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsiteEditForm({ websiteId, data, onSave }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post(`/websites/${websiteId}`, data));
|
||||
const ref = useRef(null);
|
||||
|
|
|
@ -8,13 +8,12 @@ import {
|
|||
TextField,
|
||||
} from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const CONFIRM_VALUE = 'RESET';
|
||||
|
||||
export default function WebsiteResetForm({ websiteId, onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post(`/websites/${websiteId}/reset`, data));
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Breadcrumbs, Item, Tabs, useToast, Button, Text, Icon, Icons } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import Page from 'components/layout/Page';
|
||||
|
@ -10,11 +9,11 @@ import WebsiteData from 'components/pages/settings/websites/WebsiteData';
|
|||
import TrackingCode from 'components/pages/settings/websites/TrackingCode';
|
||||
import ShareUrl from 'components/pages/settings/websites/ShareUrl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsiteSettings({ websiteId }) {
|
||||
const router = useRouter();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const [values, setValues] = useState(null);
|
||||
const [tab, setTab] = useState('details');
|
||||
const { get, useQuery } = useApi();
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Button, Icon, Text, Modal, ModalTrigger, useToast, Icons } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
|
@ -7,9 +6,10 @@ import WebsiteAddForm from 'components/pages/settings/websites/WebsiteAddForm';
|
|||
import WebsitesTable from 'components/pages/settings/websites/WebsitesTable';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsitesList() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { user } = useUser();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading, error, refetch } = useQuery(
|
||||
|
@ -18,7 +18,6 @@ export default function WebsitesList() {
|
|||
{ enabled: !!user },
|
||||
);
|
||||
const { toast, showToast } = useToast();
|
||||
const { formatMessage } = useIntl();
|
||||
const hasData = data && data.length !== 0;
|
||||
|
||||
const handleSave = async () => {
|
||||
|
|
|
@ -12,11 +12,10 @@ import {
|
|||
Icons,
|
||||
Flexbox,
|
||||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function WebsitesTable({ data = [] }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const columns = [
|
||||
{ name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Menu, Item, Icon, Button, Flexbox, Text } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Link from 'next/link';
|
||||
import { GridRow, GridColumn } from 'components/layout/Grid';
|
||||
import BrowsersTable from 'components/metrics/BrowsersTable';
|
||||
|
@ -14,8 +13,8 @@ import ScreenTable from 'components/metrics/ScreenTable';
|
|||
import EventsTable from 'components/metrics/EventsTable';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './WebsiteMenuView.module.css';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
const views = {
|
||||
url: PagesTable,
|
||||
|
@ -31,7 +30,7 @@ const views = {
|
|||
};
|
||||
|
||||
export default function WebsiteMenuView({ websiteId, websiteDomain }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
resolveUrl,
|
||||
query: { view },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
SET allow_experimental_object_type = 1;
|
||||
|
||||
-- Create Event
|
||||
CREATE TABLE event
|
||||
CREATE TABLE umami.event
|
||||
(
|
||||
website_id UUID,
|
||||
session_id UUID,
|
||||
|
@ -34,7 +34,7 @@ CREATE TABLE event
|
|||
ORDER BY (website_id, session_id, created_at)
|
||||
SETTINGS index_granularity = 8192;
|
||||
|
||||
CREATE TABLE event_queue (
|
||||
CREATE TABLE umami.event_queue (
|
||||
website_id UUID,
|
||||
session_id UUID,
|
||||
event_id UUID,
|
||||
|
@ -70,7 +70,7 @@ SETTINGS kafka_broker_list = 'domain:9092,domain:9093,domain:9094', -- input bro
|
|||
kafka_max_block_size = 1048576,
|
||||
kafka_skip_broken_messages = 1;
|
||||
|
||||
CREATE MATERIALIZED VIEW event_queue_mv TO event AS
|
||||
CREATE MATERIALIZED VIEW umami.event_queue_mv TO umami.event AS
|
||||
SELECT website_id,
|
||||
session_id,
|
||||
event_id,
|
||||
|
@ -94,9 +94,9 @@ SELECT website_id,
|
|||
event_type,
|
||||
event_name,
|
||||
created_at
|
||||
FROM event_queue;
|
||||
FROM umami.event_queue;
|
||||
|
||||
CREATE TABLE event_data
|
||||
CREATE TABLE umami.event_data
|
||||
(
|
||||
website_id UUID,
|
||||
session_id UUID,
|
||||
|
@ -115,7 +115,6 @@ CREATE TABLE event_data
|
|||
ORDER BY (website_id, event_id, event_key, created_at)
|
||||
SETTINGS index_granularity = 8192;
|
||||
|
||||
CREATE TABLE event_data_queue (
|
||||
website_id UUID,
|
||||
session_id UUID,
|
||||
event_id UUID,
|
||||
|
@ -137,7 +136,7 @@ SETTINGS kafka_broker_list = 'domain:9092,domain:9093,domain:9094', -- input bro
|
|||
kafka_max_block_size = 1048576,
|
||||
kafka_skip_broken_messages = 1;
|
||||
|
||||
CREATE MATERIALIZED VIEW event_data_queue_mv TO event_data AS
|
||||
CREATE MATERIALIZED VIEW umami.event_data_queue_mv TO umami.event_data AS
|
||||
SELECT website_id,
|
||||
session_id,
|
||||
event_id,
|
||||
|
@ -150,4 +149,4 @@ SELECT website_id,
|
|||
event_date_value,
|
||||
event_data_type,
|
||||
created_at
|
||||
FROM event_data_queue;
|
||||
FROM umami.event_data_queue;
|
|
@ -78,6 +78,31 @@ CREATE TABLE `website_event` (
|
|||
PRIMARY KEY (`event_id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `event_data` (
|
||||
`event_id` VARCHAR(36) NOT NULL,
|
||||
`website_event_id` VARCHAR(36) NOT NULL,
|
||||
`website_id` VARCHAR(36) NOT NULL,
|
||||
`session_id` VARCHAR(36) NOT NULL,
|
||||
`url_path` VARCHAR(500) NOT NULL,
|
||||
`event_name` VARCHAR(500) NOT NULL,
|
||||
`event_key` VARCHAR(500) NOT NULL,
|
||||
`event_string_value` VARCHAR(500) NOT NULL,
|
||||
`event_numeric_value` DECIMAL(19, 4) NOT NULL,
|
||||
`event_date_value` TIMESTAMP(0) NULL,
|
||||
`event_data_type` INTEGER UNSIGNED NOT NULL,
|
||||
`created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
|
||||
INDEX `event_data_created_at_idx`(`created_at`),
|
||||
INDEX `event_data_session_id_idx`(`session_id`),
|
||||
INDEX `event_data_website_id_idx`(`website_id`),
|
||||
INDEX `event_data_website_event_id_idx`(`website_event_id`),
|
||||
INDEX `event_data_website_id_website_event_id_created_at_idx`(`website_id`, `website_event_id`, `created_at`),
|
||||
INDEX `event_data_website_id_session_id_created_at_idx`(`website_id`, `session_id`, `created_at`),
|
||||
INDEX `event_data_website_id_session_id_website_event_id_created_at_idx`(`website_id`, `session_id`, `website_event_id`, `created_at`),
|
||||
PRIMARY KEY (`event_id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `team` (
|
||||
`team_id` VARCHAR(36) NOT NULL,
|
||||
|
|
|
@ -17,8 +17,8 @@ model User {
|
|||
updatedAt DateTime? @map("updated_at") @db.Timestamp(0)
|
||||
deletedAt DateTime? @map("deleted_at") @db.Timestamp(0)
|
||||
|
||||
website Website[]
|
||||
teamUser TeamUser[]
|
||||
Website Website[]
|
||||
|
||||
@@map("user")
|
||||
}
|
||||
|
@ -38,6 +38,9 @@ model Session {
|
|||
city String? @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||
|
||||
websiteEvent WebsiteEvent[]
|
||||
eventData EventData[]
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([websiteId])
|
||||
@@map("session")
|
||||
|
@ -56,6 +59,7 @@ model Website {
|
|||
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
teamWebsite TeamWebsite[]
|
||||
eventData EventData[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([createdAt])
|
||||
|
@ -77,6 +81,9 @@ model WebsiteEvent {
|
|||
eventType Int @default(1) @map("event_type") @db.UnsignedInt
|
||||
eventName String? @map("event_name") @db.VarChar(50)
|
||||
|
||||
eventData EventData[]
|
||||
session Session @relation(fields: [sessionId], references: [id])
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([sessionId])
|
||||
@@index([websiteId])
|
||||
|
@ -85,6 +92,34 @@ model WebsiteEvent {
|
|||
@@map("website_event")
|
||||
}
|
||||
|
||||
model EventData {
|
||||
id String @id() @map("event_id") @db.VarChar(36)
|
||||
websiteEventId String @map("website_event_id") @db.VarChar(36)
|
||||
websiteId String @map("website_id") @db.VarChar(36)
|
||||
sessionId String @map("session_id") @db.VarChar(36)
|
||||
urlPath String @map("url_path") @db.VarChar(500)
|
||||
eventName String @map("event_name") @db.VarChar(500)
|
||||
eventKey String @map("event_key") @db.VarChar(500)
|
||||
eventStringValue String @map("event_string_value") @db.VarChar(500)
|
||||
eventNumericValue Decimal @map("event_numeric_value") @db.Decimal(19,4)
|
||||
eventDateValue DateTime? @map("event_date_value") @db.Timestamp(0)
|
||||
eventDataType Int @map("event_data_type") @db.UnsignedInt
|
||||
createdAt DateTime? @default(now()) @map("created_at")@db.Timestamp(0)
|
||||
|
||||
website Website @relation(fields: [websiteId], references: [id])
|
||||
websiteEvent WebsiteEvent @relation(fields: [websiteEventId], references: [id])
|
||||
session Session @relation(fields: [sessionId], references: [id])
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([sessionId])
|
||||
@@index([websiteId])
|
||||
@@index([websiteEventId])
|
||||
@@index([websiteId, websiteEventId, createdAt])
|
||||
@@index([websiteId, sessionId, createdAt])
|
||||
@@index([websiteId, sessionId, websiteEventId, createdAt])
|
||||
@@map("event_data")
|
||||
}
|
||||
|
||||
model Team {
|
||||
id String @id() @unique() @map("team_id") @db.VarChar(36)
|
||||
name String @db.VarChar(50)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "event_data" (
|
||||
"event_id" UUID NOT NULL,
|
||||
"website_event_id" UUID NOT NULL,
|
||||
"website_id" UUID NOT NULL,
|
||||
"session_id" UUID NOT NULL,
|
||||
"url_path" VARCHAR(500) NOT NULL,
|
||||
"event_name" VARCHAR(500) NOT NULL,
|
||||
"event_key" VARCHAR(500) NOT NULL,
|
||||
"event_string_value" VARCHAR(500) NOT NULL,
|
||||
"event_numeric_value" DECIMAL(19,4) NOT NULL,
|
||||
"event_date_value" TIMESTAMPTZ(6),
|
||||
"event_data_type" INTEGER NOT NULL,
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "event_data_pkey" PRIMARY KEY ("event_id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_data_created_at_idx" ON "event_data"("created_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_data_session_id_idx" ON "event_data"("session_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_data_website_id_idx" ON "event_data"("website_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_data_website_event_id_idx" ON "event_data"("website_event_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_data_website_id_website_event_id_created_at_idx" ON "event_data"("website_id", "website_event_id", "created_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_data_website_id_session_id_created_at_idx" ON "event_data"("website_id", "session_id", "created_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_data_website_id_session_id_website_event_id_created_a_idx" ON "event_data"("website_id", "session_id", "website_event_id", "created_at");
|
|
@ -162,4 +162,4 @@ model TeamWebsite {
|
|||
@@index([teamId])
|
||||
@@index([websiteId])
|
||||
@@map("team_website")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { messages, labels } from 'components/messages';
|
||||
|
||||
export default function useMessages() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
function getMessage(id) {
|
||||
const message = Object.values(messages).find(value => value.id === id);
|
||||
|
||||
return message ? formatMessage(message) : id;
|
||||
}
|
||||
|
||||
return { formatMessage, FormattedMessage, messages, labels, getMessage };
|
||||
}
|
|
@ -1,25 +1,23 @@
|
|||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
export default function useSticky({ scrollElement = document, defaultSticky = false }) {
|
||||
const [isSticky, setIsSticky] = useState(defaultSticky);
|
||||
export default function useSticky({ enabled = true, threshold = 1 }) {
|
||||
const [isSticky, setIsSticky] = useState(false);
|
||||
const ref = useRef(null);
|
||||
const initialTop = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsSticky((scrollElement?.scrollTop ?? window.scrollY) > initialTop.current);
|
||||
};
|
||||
let observer;
|
||||
const handler = ([entry]) => setIsSticky(entry.intersectionRatio < threshold);
|
||||
|
||||
if (initialTop.current === null) {
|
||||
initialTop.current = ref?.current?.offsetTop;
|
||||
if (enabled && ref.current) {
|
||||
observer = new IntersectionObserver(handler, { threshold: [threshold] });
|
||||
observer.observe(ref.current);
|
||||
}
|
||||
|
||||
scrollElement.addEventListener('scroll', handleScroll, true);
|
||||
|
||||
return () => {
|
||||
scrollElement.removeEventListener('scroll', handleScroll, true);
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
};
|
||||
}, [ref, setIsSticky, scrollElement]);
|
||||
}, [ref, enabled, threshold]);
|
||||
|
||||
return { ref, isSticky };
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"node-fetch": "^3.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react": "^18.2.0",
|
||||
"react-basics": "^0.73.0",
|
||||
"react-basics": "^0.74.0",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-intl": "^5.24.7",
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Row, Column, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import AppLayout from 'components/layout/AppLayout';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function Custom404() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
|
|
|
@ -29,7 +29,7 @@ export default function App({ Component, pageProps }) {
|
|||
|
||||
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
|
||||
|
||||
if (!config || config.uiDisabled) {
|
||||
if (config?.uiDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -115,11 +115,11 @@ async function clickhouseQuery(data: {
|
|||
subdivision1: subdivision1 ? subdivision1 : null,
|
||||
subdivision2: subdivision2 ? subdivision2 : null,
|
||||
city: city ? city : null,
|
||||
urlPath: urlPath?.substring(0, URL_LENGTH),
|
||||
urlQuery: urlQuery?.substring(0, URL_LENGTH),
|
||||
referrerPath: referrerPath?.substring(0, URL_LENGTH),
|
||||
referrerQuery: referrerQuery?.substring(0, URL_LENGTH),
|
||||
referrerDomain: referrerDomain?.substring(0, URL_LENGTH),
|
||||
url_path: urlPath?.substring(0, URL_LENGTH),
|
||||
url_query: urlQuery?.substring(0, URL_LENGTH),
|
||||
referrer_path: referrerPath?.substring(0, URL_LENGTH),
|
||||
referrer_query: referrerQuery?.substring(0, URL_LENGTH),
|
||||
referrer_domain: referrerDomain?.substring(0, URL_LENGTH),
|
||||
page_title: pageTitle,
|
||||
event_type: EVENT_TYPE.pageView,
|
||||
created_at: getDateFormat(new Date()),
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
html {
|
||||
overflow-x: hidden;
|
||||
margin-right: calc(-1 * (100vw - 100%));
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans,
|
||||
|
@ -64,7 +59,7 @@ svg {
|
|||
#__next {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
@ -7136,10 +7136,10 @@ rc@^1.2.7:
|
|||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-basics@^0.73.0:
|
||||
version "0.73.0"
|
||||
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.73.0.tgz#9555563f3407ac417dc833dfca47588123d55535"
|
||||
integrity sha512-eEK8yWWrXO7JATBlPKBfFQlD1hNZoNeEtlYNx+QjOCLKu1qjClutP5nXWHmX4gHE97XFwUKzbTU35NkNEy5C0w==
|
||||
react-basics@^0.74.0:
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.74.0.tgz#153433bc485d6b71d8edf377d1a83f1d55133e24"
|
||||
integrity sha512-Z9XwgEOSRvcPqFqFZL6HR59t/XrqhIB8uoYwbmon3IFX2W0kOPqkX1Box0c+2BibJoHp4N4mbfuZWK2kSEnq9g==
|
||||
dependencies:
|
||||
classnames "^2.3.1"
|
||||
date-fns "^2.29.3"
|
||||
|
|
Loading…
Reference in New Issue