mirror of
https://github.com/kremalicious/umami.git
synced 2025-01-11 13:44:01 +01:00
Refactored fetching to use react-query.
This commit is contained in:
parent
7bbed0e12b
commit
c56f02c475
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
startOfWeek,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
@ -4,7 +4,7 @@ import { endOfYear, isSameDay } from 'date-fns';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { dateFormat } from 'lib/date';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Icon, Modal } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DropDown from './DropDown';
|
||||
|
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Dot.module.css';
|
||||
|
||||
function Dot({ color, size, className }) {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div
|
||||
style={{ background: color }}
|
||||
className={classNames(styles.dot, className, {
|
||||
[styles.small]: size === 'small',
|
||||
[styles.large]: size === 'large',
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Dot.propTypes = {
|
||||
color: PropTypes.string,
|
||||
size: PropTypes.oneOf(['small', 'large']),
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Dot;
|
@ -1,22 +0,0 @@
|
||||
.wrapper {
|
||||
background: var(--base50);
|
||||
margin-right: 10px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.dot {
|
||||
background: var(--green400);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.dot.small {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dot.large {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import Menu from './Menu';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon, Flexbox } from 'react-basics';
|
||||
import Logo from 'assets/logo.svg';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import Exclamation from 'assets/exclamation-triangle.svg';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import styles from './ErrorMessage.module.css';
|
||||
import { Icon } from 'react-basics';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './Favicon.module.css';
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||
import { ButtonGroup } from 'react-basics';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Icon.module.css';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import NextLink from 'next/link';
|
||||
|
@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Loading.module.css';
|
||||
|
||||
function Loading({ className, overlay = false }) {
|
||||
return (
|
||||
<div className={classNames(styles.loading, { [styles.overlay]: overlay }, className)}>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading.propTypes = {
|
||||
className: PropTypes.string,
|
||||
overlay: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Loading;
|
@ -1,55 +0,0 @@
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.loading.overlay {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
background: var(--base400);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.loading div {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 100%;
|
||||
background: var(--base400);
|
||||
animation: blink 1.4s infinite;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.loading.overlay div {
|
||||
background: var(--base900);
|
||||
}
|
||||
|
||||
.loading div + div {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.loading div:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.loading div:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Menu.module.css';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import Menu from 'components/common/Menu';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useRouter } from 'next/router';
|
||||
import classNames from 'classnames';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
import styles from './OverflowText.module.css';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import useStore from 'store/queries';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Tag.module.css';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
@ -2,7 +2,7 @@ import Calendar from 'components/common/Calendar';
|
||||
import { FormButtons } from 'components/layout/FormLayout';
|
||||
import { isAfter, isBefore, isSameDay } from 'date-fns';
|
||||
import { getDateRangeValues } from 'lib/date';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Button, ButtonGroup } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import styles from './DatePickerForm.module.css';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import { getRandomChars, useApi } from 'next-basics';
|
||||
import { getRandomChars } from 'next-basics';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
@ -12,11 +11,12 @@ import {
|
||||
TextField,
|
||||
Toggle,
|
||||
} from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
|
||||
export default function ShareUrlForm({ websiteId, data, onSave }) {
|
||||
const { name, shareId } = data;
|
||||
const [id, setId] = useState(shareId);
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const { post } = useApi();
|
||||
const { mutate, error } = useMutation(({ shareId }) =>
|
||||
post(`/websites/${websiteId}`, { shareId }),
|
||||
);
|
||||
|
@ -3,10 +3,9 @@ import { Form, FormInput, FormButtons, TextField, Button } from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import styles from './Form.module.css';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
|
||||
export default function TeamAddForm({ onSave, onClose }) {
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const { post } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => post('/teams', data));
|
||||
const ref = useRef(null);
|
||||
|
||||
|
@ -2,10 +2,9 @@ import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from '
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useRef } from 'react';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
|
||||
export default function TeamEditForm({ teamId, data, onSave }) {
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const { post } = useApi();
|
||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data));
|
||||
const ref = useRef(null);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { Button, Form, FormButtons, FormInput, SubmitButton, TextField } from 'react-basics';
|
||||
import styles from './Form.module.css';
|
||||
@ -7,7 +6,7 @@ import styles from './Form.module.css';
|
||||
const CONFIRM_VALUE = 'DELETE';
|
||||
|
||||
export default function UserDeleteForm({ userId, onSave, onClose }) {
|
||||
const { del } = useApi(getClientAuthToken());
|
||||
const { del } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => del(`/users/${userId}`, data));
|
||||
|
||||
const handleSubmit = async data => {
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
import { useRef } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import styles from './UserForm.module.css';
|
||||
|
||||
@ -27,7 +26,7 @@ const items = [
|
||||
|
||||
export default function UserEditForm({ data, onSave }) {
|
||||
const { id } = data;
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const { post } = useApi();
|
||||
const { mutate, error } = useMutation(({ username }) => post(`/user/${id}`, { username }));
|
||||
const ref = useRef(null);
|
||||
|
||||
|
@ -2,18 +2,15 @@ import { useRef } from 'react';
|
||||
import { Form, FormInput, FormButtons, PasswordField, Button } from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import styles from './UserPasswordForm.module.css';
|
||||
import useUser from 'hooks/useUser';
|
||||
|
||||
export default function UserPasswordForm({ onSave, userId }) {
|
||||
const {
|
||||
user: { id },
|
||||
} = useUser();
|
||||
const user = useUser();
|
||||
|
||||
const isCurrentUser = !userId || id === userId;
|
||||
const url = isCurrentUser ? `/users/${id}/password` : `/users/${id}`;
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const isCurrentUser = !userId || user?.id === userId;
|
||||
const url = isCurrentUser ? `/users/${user?.id}/password` : `/users/${user?.id}`;
|
||||
const { post } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => post(url, data));
|
||||
const ref = useRef(null);
|
||||
|
||||
|
@ -3,11 +3,10 @@ import { Form, FormInput, FormButtons, TextField, Button, SubmitButton } from 'r
|
||||
import useApi from 'hooks/useApi';
|
||||
import styles from './Form.module.css';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import { DOMAIN_REGEX } from 'lib/constants';
|
||||
|
||||
export default function WebsiteAddForm({ onSave, onClose }) {
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const { post } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => post('/websites', data));
|
||||
const ref = useRef(null);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { Button, Form, FormButtons, FormInput, SubmitButton, TextField } from 'react-basics';
|
||||
import styles from './Form.module.css';
|
||||
@ -7,7 +6,7 @@ import styles from './Form.module.css';
|
||||
const CONFIRM_VALUE = 'DELETE';
|
||||
|
||||
export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
|
||||
const { del } = useApi(getClientAuthToken());
|
||||
const { del } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => del(`/websites/${websiteId}`, data));
|
||||
|
||||
const handleSubmit = async data => {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { Button, Form, FormButtons, FormInput, SubmitButton, TextField } from 'react-basics';
|
||||
import styles from './Form.module.css';
|
||||
@ -7,7 +6,7 @@ import styles from './Form.module.css';
|
||||
const CONFIRM_VALUE = 'RESET';
|
||||
|
||||
export default function WebsiteResetForm({ websiteId, onSave, onClose }) {
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const { post } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data =>
|
||||
post(`/websites/${websiteId}/reset`, data),
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
|
||||
function isInViewport(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default function StickyHeader({
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './ButtonLayout.module.css';
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useSpring, animated } from 'react-spring';
|
||||
import classNames from 'classnames';
|
||||
import { ErrorMessage } from 'formik';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './GridLayout.module.css';
|
||||
|
||||
|
@ -15,7 +15,7 @@ import SettingsButton from '../settings/SettingsButton';
|
||||
import styles from './Header.module.css';
|
||||
|
||||
export default function Header() {
|
||||
const { user } = useUser();
|
||||
const user = useUser();
|
||||
const { pathname } = useRouter();
|
||||
const { updatesDisabled, adminDisabled } = useConfig();
|
||||
const isSharePage = pathname.includes('/share/');
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import classNames from 'classnames';
|
||||
import NavMenu from 'components/common/NavMenu';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
|
@ -1,15 +1,17 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import Dot from 'components/common/Dot';
|
||||
import useApi from 'hooks/useApi';
|
||||
import styles from './ActiveUsers.module.css';
|
||||
|
||||
export default function ActiveUsers({ websiteId, className, value, interval = 60000 }) {
|
||||
export default function ActiveUsers({ websiteId, className, value, refetchInterval = 60000 }) {
|
||||
const url = websiteId ? `/websites/${websiteId}/active` : null;
|
||||
const { data } = useFetch(url, {
|
||||
interval,
|
||||
const { get, useQuery } = useApi();
|
||||
const { data } = useQuery(['websites:active', websiteId], () => get(url), {
|
||||
refetchInterval,
|
||||
});
|
||||
|
||||
const count = useMemo(() => {
|
||||
if (websiteId) {
|
||||
return data?.[0]?.x || 0;
|
||||
@ -24,7 +26,7 @@ export default function ActiveUsers({ websiteId, className, value, interval = 60
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className)}>
|
||||
<Dot />
|
||||
<StatusLight variant="success" />
|
||||
<div className={styles.text}>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import ChartJS from 'chart.js';
|
||||
import Legend from 'components/metrics/Legend';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import MetricsTable from 'components/metrics/MetricsTable';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import Dot from 'components/common/Dot';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import styles from './ChartTooltip.module.css';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
@ -16,7 +15,7 @@ export default function ChartTooltip({ chartId, tooltip }) {
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.metric}>
|
||||
<Dot color={labelColor} />
|
||||
<StatusLight color={labelColor} />
|
||||
{value} {label}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { useSpring, animated, config } from 'react-spring';
|
||||
import classNames from 'classnames';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { getDeviceMessage } from 'components/messages';
|
||||
|
@ -1,39 +1,37 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Loading } from 'react-basics';
|
||||
import { colord } from 'colord';
|
||||
import BarChart from './BarChart';
|
||||
import { getDateArray, getDateLength } from 'lib/date';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import useTimezone from 'hooks/useTimezone';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { EVENT_COLORS } from 'lib/constants';
|
||||
|
||||
export default function EventsChart({ websiteId, className, token }) {
|
||||
const { get, useQuery } = useApi();
|
||||
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
|
||||
const [timezone] = useTimezone();
|
||||
const {
|
||||
query: { url, eventName },
|
||||
} = usePageQuery();
|
||||
|
||||
const { data, loading } = useFetch(
|
||||
`/websites/${websiteId}/events`,
|
||||
{
|
||||
params: {
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
unit,
|
||||
tz: timezone,
|
||||
url,
|
||||
eventName,
|
||||
token,
|
||||
},
|
||||
},
|
||||
[modified, eventName],
|
||||
const { data, isLoading } = useQuery(['events', { websiteId, modified, eventName }], () =>
|
||||
get(`/websites/${websiteId}/events`, {
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
unit,
|
||||
timezone,
|
||||
url,
|
||||
eventName,
|
||||
token,
|
||||
}),
|
||||
);
|
||||
|
||||
const datasets = useMemo(() => {
|
||||
if (!data) return [];
|
||||
if (loading) return data;
|
||||
if (isLoading) return data;
|
||||
|
||||
const map = data.reduce((obj, { x, t, y }) => {
|
||||
if (!obj[x]) {
|
||||
@ -60,7 +58,7 @@ export default function EventsChart({ websiteId, className, token }) {
|
||||
borderWidth: 1,
|
||||
};
|
||||
});
|
||||
}, [data, loading]);
|
||||
}, [data, isLoading]);
|
||||
|
||||
function handleUpdate(chart) {
|
||||
chart.data.datasets = datasets;
|
||||
@ -68,6 +66,10 @@ export default function EventsChart({ websiteId, className, token }) {
|
||||
chart.update();
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading variant="dots" />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
@ -81,7 +83,7 @@ export default function EventsChart({ websiteId, className, token }) {
|
||||
height={300}
|
||||
records={getDateLength(startDate, endDate, unit)}
|
||||
onUpdate={handleUpdate}
|
||||
loading={loading}
|
||||
loading={isLoading}
|
||||
stacked
|
||||
/>
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import { Button } from 'react-basics';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import { colord } from 'colord';
|
||||
import classNames from 'classnames';
|
||||
import Dot from 'components/common/Dot';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useForceUpdate from 'hooks/useForceUpdate';
|
||||
import styles from './Legend.module.css';
|
||||
@ -35,7 +34,7 @@ export default function Legend({ chart }) {
|
||||
className={classNames(styles.label, { [styles.hidden]: hidden })}
|
||||
onClick={() => handleClick(datasetIndex)}
|
||||
>
|
||||
<Dot color={color.alpha(color.alpha() + 0.2).toHex()} />
|
||||
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()} />
|
||||
<span className={locale}>{text}</span>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useSpring, animated } from 'react-spring';
|
||||
import { formatNumber } from 'lib/format';
|
||||
import styles from './MetricCard.module.css';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Loading } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Loading from 'components/common/Loading';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
|
||||
@ -11,6 +11,7 @@ import MetricCard from './MetricCard';
|
||||
import styles from './MetricsBar.module.css';
|
||||
|
||||
export default function MetricsBar({ websiteId, className }) {
|
||||
const { get, useQuery } = useApi();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
const { startDate, endDate, modified } = dateRange;
|
||||
const [format, setFormat] = useState(true);
|
||||
@ -18,10 +19,10 @@ export default function MetricsBar({ websiteId, className }) {
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
|
||||
const { data, error, loading } = useFetch(
|
||||
`/websites/${websiteId}/stats`,
|
||||
{
|
||||
params: {
|
||||
const { data, error, isLoading } = useQuery(
|
||||
['websites:stats', { websiteId, modified, url, referrer, os, browser, device, country }],
|
||||
() =>
|
||||
get(`/websites/${websiteId}/stats`, {
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
url,
|
||||
@ -30,9 +31,7 @@ export default function MetricsBar({ websiteId, className }) {
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
},
|
||||
},
|
||||
[modified, url, referrer, os, browser, device, country],
|
||||
}),
|
||||
);
|
||||
|
||||
const formatFunc = format
|
||||
@ -54,7 +53,7 @@ export default function MetricsBar({ websiteId, className }) {
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.bar, className)} onClick={handleSetFormat}>
|
||||
{!data && loading && <Loading />}
|
||||
{isLoading && <Loading variant="dots" />}
|
||||
{error && <ErrorMessage />}
|
||||
{data && !error && (
|
||||
<>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { Loading } from 'react-basics';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import firstBy from 'thenby';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'components/common/Link';
|
||||
import Loading from 'components/common/Loading';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useApi from 'hooks/useApi';
|
||||
import Arrow from 'assets/arrow-right.svg';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
@ -36,11 +36,15 @@ export default function MetricsTable({
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
const { formatMessage } = useIntl();
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
const { data, loading, error } = useFetch(
|
||||
`/websites/${websiteId}/metrics`,
|
||||
{
|
||||
params: {
|
||||
const { data, isLoading, error } = useQuery(
|
||||
[
|
||||
'websites:mnetrics',
|
||||
{ websiteId, type, modified, url, referrer, os, browser, device, country },
|
||||
],
|
||||
() =>
|
||||
get(`/websites/${websiteId}/metrics`, {
|
||||
type,
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
@ -50,11 +54,8 @@ export default function MetricsTable({
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
},
|
||||
onDataLoad,
|
||||
delay: delay || DEFAULT_ANIMATION_DURATION,
|
||||
},
|
||||
[type, modified, url, referrer, os, browser, device, country],
|
||||
}),
|
||||
{ onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION },
|
||||
);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
@ -73,7 +74,7 @@ export default function MetricsTable({
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className)}>
|
||||
{!data && loading && <Loading />}
|
||||
{!data && isLoading && <Loading variant="dots" />}
|
||||
{error && <ErrorMessage />}
|
||||
{data && !error && <DataTable {...props} data={filteredData} className={className} />}
|
||||
<div className={styles.footer}>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useIntl, defineMessage } from 'react-intl';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { colord } from 'colord';
|
||||
import CheckVisible from 'components/helpers/CheckVisible';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { format, parseISO, startOfMinute, subMinutes, isBefore } from 'date-fns';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import { getDateArray } from 'lib/date';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { differenceInMinutes } from 'date-fns';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import firstBy from 'thenby';
|
||||
import { Icon } from 'react-basics';
|
||||
import Dot from 'components/common/Dot';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import NoData from 'components/common/NoData';
|
||||
import { getDeviceMessage, labels } from 'components/messages';
|
||||
@ -149,7 +149,7 @@ export default function RealtimeLog({ data, websites, websiteId }) {
|
||||
return (
|
||||
<div className={styles.row} style={style}>
|
||||
<div>
|
||||
<Dot color={getColor(row)} />
|
||||
<StatusLight color={getColor(row)} />
|
||||
</div>
|
||||
<div className={styles.time}>{getTime(row)}</div>
|
||||
<div className={styles.detail}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import { useMemo, useState, useCallback } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import firstBy from 'thenby';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Row, Column } from 'react-basics';
|
||||
import { Row, Column, Loading } from 'react-basics';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import MetricsBar from './MetricsBar';
|
||||
import WebsiteHeader from './WebsiteHeader';
|
||||
@ -7,12 +7,11 @@ import DateFilter from 'components/common/DateFilter';
|
||||
import StickyHeader from 'components/helpers/StickyHeader';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import FilterTags from 'components/metrics/FilterTags';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import useTimezone from 'hooks/useTimezone';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { getDateArray, getDateLength, getDateRangeValues } from 'lib/date';
|
||||
import useApi from 'hooks/useApi';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
|
||||
export default function WebsiteChart({
|
||||
@ -31,26 +30,24 @@ export default function WebsiteChart({
|
||||
resolve,
|
||||
query: { url, referrer, os, browser, device, country },
|
||||
} = usePageQuery();
|
||||
const { get } = useApi();
|
||||
const { get, useQuery } = useApi();
|
||||
|
||||
const { data, loading, error } = useFetch(
|
||||
`/websites/${websiteId}/pageviews`,
|
||||
{
|
||||
params: {
|
||||
const { data, isLoading, error } = useQuery(
|
||||
['websites:pageviews', { websiteId, modified, url, referrer, os, browser, device, country }],
|
||||
() =>
|
||||
get(`/websites/${websiteId}/pageviews`, {
|
||||
startAt: +startDate,
|
||||
endAt: +endDate,
|
||||
unit,
|
||||
tz: timezone,
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
},
|
||||
onDataLoad,
|
||||
},
|
||||
[modified, url, referrer, os, browser, device, country],
|
||||
}),
|
||||
{ onSuccess: onDataLoad },
|
||||
);
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
@ -78,10 +75,13 @@ export default function WebsiteChart({
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading variant="dots" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebsiteHeader websiteId={websiteId} title={title} domain={domain} />
|
||||
|
||||
<StickyHeader
|
||||
className={styles.metrics}
|
||||
stickyClassName={styles.sticky}
|
||||
@ -114,7 +114,7 @@ export default function WebsiteChart({
|
||||
data={chartData}
|
||||
unit={unit}
|
||||
records={getDateLength(startDate, endDate, unit)}
|
||||
loading={loading}
|
||||
loading={isLoading}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
|
@ -6,19 +6,21 @@ import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Icon, Item, Menu, Text } from 'react-basics';
|
||||
import styles from './Nav.module.css';
|
||||
import useRequireLogin from 'hooks/useRequireLogin';
|
||||
import useUser from 'hooks/useUser';
|
||||
|
||||
export default function Nav() {
|
||||
const {
|
||||
user: { role },
|
||||
} = useRequireLogin();
|
||||
const user = useUser();
|
||||
const { pathname } = useRouter();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSelect = () => {};
|
||||
|
||||
const items = [
|
||||
{ icon: <Website />, label: 'Websites', url: '/websites' },
|
||||
{ icon: <User />, label: 'Users', url: '/users', hidden: role !== 'admin' },
|
||||
{ icon: <User />, label: 'Users', url: '/users', hidden: !user.isAdmin },
|
||||
{ icon: <Team />, label: 'Teams', url: '/teams' },
|
||||
{ icon: <User />, label: 'Profile', url: '/profile' },
|
||||
];
|
||||
|
@ -3,9 +3,9 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import WebsiteList from 'components/pages/WebsiteList';
|
||||
import { Button } from 'react-basics';
|
||||
import { Button, Loading } from 'react-basics';
|
||||
import DashboardSettingsButton from 'components/settings/DashboardSettingsButton';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useDashboard from 'store/dashboard';
|
||||
import DashboardEdit from './DashboardEdit';
|
||||
import styles from './WebsiteList.module.css';
|
||||
@ -19,13 +19,18 @@ export default function Dashboard({ userId }) {
|
||||
const dashboard = useDashboard();
|
||||
const { showCharts, limit, editing } = dashboard;
|
||||
const [max, setMax] = useState(limit);
|
||||
const { data } = useFetch('/websites', { params: { user_id: userId } });
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading } = useQuery(['websites'], () => get('/websites', { userId }));
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
function handleMore() {
|
||||
setMax(max + limit);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { subMinutes, startOfMinute } from 'date-fns';
|
||||
import firstBy from 'thenby';
|
||||
@ -10,7 +10,7 @@ import RealtimeHeader from 'components/metrics/RealtimeHeader';
|
||||
import WorldMap from 'components/common/WorldMap';
|
||||
import DataTable from 'components/metrics/DataTable';
|
||||
import RealtimeViews from 'components/metrics/RealtimeViews';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import useCountryNames from 'hooks/useCountryNames';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
@ -33,13 +33,17 @@ export default function RealtimeDashboard() {
|
||||
const countryNames = useCountryNames(locale);
|
||||
const [data, setData] = useState();
|
||||
const [websiteId, setWebsiteId] = useState(null);
|
||||
const { data: init, loading } = useFetch('/realtime/init');
|
||||
const { data: updates } = useFetch('/realtime/update', {
|
||||
params: { startAt: data?.timestamp },
|
||||
disabled: !init?.websites?.length || !data,
|
||||
interval: REALTIME_INTERVAL,
|
||||
headers: { [SHARE_TOKEN_HEADER]: init?.token },
|
||||
});
|
||||
const { get, useQuery } = useApi();
|
||||
const { data: init, isLoading } = useQuery(['realtime:init'], () => get('/realtime/init'));
|
||||
const { data: updates } = useQuery(
|
||||
['realtime:updates'],
|
||||
() =>
|
||||
get('/realtime/update', { startAt: data?.timestamp }, { [SHARE_TOKEN_HEADER]: init?.token }),
|
||||
{
|
||||
disabled: !init?.websites?.length || !data,
|
||||
retryInterval: REALTIME_INTERVAL,
|
||||
},
|
||||
);
|
||||
|
||||
const renderCountryName = useCallback(
|
||||
({ x }) => <span className={locale}>{countryNames[x]}</span>,
|
||||
@ -108,7 +112,7 @@ export default function RealtimeDashboard() {
|
||||
}
|
||||
}, [updates]);
|
||||
|
||||
if (!init || !data || loading) {
|
||||
if (!init || !data || isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Layout from 'components/layout/Layout';
|
||||
import Menu from 'components/nav/Nav';
|
||||
import useRequireLogin from 'hooks/useRequireLogin';
|
||||
import useUser from 'hooks/useUser';
|
||||
import styles from './Settings.module.css';
|
||||
|
||||
export default function Settings({ children }) {
|
||||
const { user: loggedIn } = useRequireLogin();
|
||||
const user = useUser();
|
||||
|
||||
if (!loggedIn) {
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -6,13 +6,12 @@ import Link from 'next/link';
|
||||
import Page from 'components/layout/Page';
|
||||
import TeamEditForm from 'components/forms/TeamEditForm';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import TeamMembersTable from '../tables/TeamMembersTable';
|
||||
|
||||
export default function TeamDetails({ teamId }) {
|
||||
const [values, setValues] = useState(null);
|
||||
const [tab, setTab] = useState('general');
|
||||
const { get } = useApi(getClientAuthToken());
|
||||
const { get } = useApi();
|
||||
const { toast, showToast } = useToast();
|
||||
const { data, isLoading } = useQuery(
|
||||
['team', teamId],
|
||||
|
@ -6,13 +6,12 @@ import TeamAddForm from 'components/forms/TeamAddForm';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import TeamsTable from 'components/tables/TeamsTable';
|
||||
import Page from 'components/layout/Page';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export default function TeamsList() {
|
||||
const [edit, setEdit] = useState(false);
|
||||
const [update, setUpdate] = useState(0);
|
||||
const { get } = useApi(getClientAuthToken());
|
||||
const { get } = useApi();
|
||||
const { data, isLoading, error } = useQuery(['teams', update], () => get(`/teams`));
|
||||
const hasData = data && data.length !== 0;
|
||||
const { toast, showToast } = useToast();
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { Button, Column, Loading, Row } from 'react-basics';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import EventsChart from 'components/metrics/EventsChart';
|
||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Button, Column, Row } from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import styles from './TestConsole.module.css';
|
||||
|
||||
export default function TestConsole() {
|
||||
const { data } = useFetch('/websites?include_all=true');
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading } = useQuery(['websites:test-console'], () =>
|
||||
get('/websites?include_all=true'),
|
||||
);
|
||||
const router = useRouter();
|
||||
const {
|
||||
basePath,
|
||||
@ -19,6 +22,10 @@ export default function TestConsole() {
|
||||
} = router;
|
||||
const websiteId = id?.[0];
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import UserEditForm from 'components/forms/UserEditForm';
|
||||
import UserPasswordForm from 'components/forms/UserPasswordForm';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import useApi from 'hooks/useApi';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -14,7 +13,7 @@ import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics';
|
||||
export default function UserSettings({ userId }) {
|
||||
const [values, setValues] = useState(null);
|
||||
const [tab, setTab] = useState('general');
|
||||
const { get } = useApi(getClientAuthToken());
|
||||
const { get } = useApi();
|
||||
const { toast, showToast } = useToast();
|
||||
const router = useRouter();
|
||||
const { data, isLoading } = useQuery(
|
||||
|
@ -3,7 +3,6 @@ import PageHeader from 'components/layout/PageHeader';
|
||||
import UsersTable from 'components/tables/UsersTable';
|
||||
import { useState } from 'react';
|
||||
import { Button, Icon, useToast } from 'react-basics';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import useApi from 'hooks/useApi';
|
||||
|
||||
@ -11,7 +10,7 @@ export default function UsersList() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const { toast, showToast } = useToast();
|
||||
const { post } = useApi(getClientAuthToken());
|
||||
const { post } = useApi();
|
||||
const { mutate, isLoading } = useMutation(data => post('/api-key', data));
|
||||
|
||||
const handleSave = () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Column } from 'react-basics';
|
||||
import { Column, Loading } from 'react-basics';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import WebsiteChart from 'components/metrics/WebsiteChart';
|
||||
@ -8,7 +8,6 @@ import Page from 'components/layout/Page';
|
||||
import GridRow from 'components/layout/GridRow';
|
||||
import MenuLayout from 'components/layout/MenuLayout';
|
||||
import Link from 'components/common/Link';
|
||||
import Loading from 'components/common/Loading';
|
||||
import PagesTable from 'components/metrics/PagesTable';
|
||||
import ReferrersTable from 'components/metrics/ReferrersTable';
|
||||
import BrowsersTable from 'components/metrics/BrowsersTable';
|
||||
@ -18,8 +17,8 @@ import CountriesTable from 'components/metrics/CountriesTable';
|
||||
import LanguagesTable from 'components/metrics/LanguagesTable';
|
||||
import ScreenTable from 'components/metrics/ScreenTable';
|
||||
import QueryParametersTable from 'components/metrics/QueryParametersTable';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||
import Arrow from 'assets/arrow-right.svg';
|
||||
import styles from './WebsiteDetails.module.css';
|
||||
@ -49,7 +48,10 @@ const views = {
|
||||
};
|
||||
|
||||
export default function WebsiteDetails({ websiteId }) {
|
||||
const { data } = useFetch(`/websites/${websiteId}`);
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading } = useQuery(['websites', websiteId], () =>
|
||||
get(`/websites/${websiteId}`),
|
||||
);
|
||||
const [chartLoaded, setChartLoaded] = useState(false);
|
||||
const [countryData, setCountryData] = useState();
|
||||
const {
|
||||
@ -122,6 +124,10 @@ export default function WebsiteDetails({ websiteId }) {
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
@ -136,7 +142,7 @@ export default function WebsiteDetails({ websiteId }) {
|
||||
showLink={false}
|
||||
stickyHeader
|
||||
/>
|
||||
{!chartLoaded && <Loading />}
|
||||
{!chartLoaded && <Loading variant="dots" />}
|
||||
{chartLoaded && !view && (
|
||||
<>
|
||||
<GridRow>
|
||||
|
@ -9,13 +9,12 @@ import WebsiteReset from 'components/forms/WebsiteReset';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import TrackingCodeForm from 'components/forms/TrackingCodeForm';
|
||||
import ShareUrlForm from 'components/forms/ShareUrlForm';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import ExternalLink from 'assets/external-link.svg';
|
||||
|
||||
export default function Websites({ websiteId }) {
|
||||
const [values, setValues] = useState(null);
|
||||
const [tab, setTab] = useState('general');
|
||||
const { get } = useApi(getClientAuthToken());
|
||||
const { get } = useApi();
|
||||
const { toast, showToast } = useToast();
|
||||
const { data, isLoading } = useQuery(
|
||||
['website', websiteId],
|
||||
|
@ -7,14 +7,13 @@ import WebsiteAddForm from 'components/forms/WebsiteAddForm';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import WebsitesTable from 'components/tables/WebsitesTable';
|
||||
import Page from 'components/layout/Page';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import useUser from 'hooks/useUser';
|
||||
|
||||
export default function WebsitesList() {
|
||||
const [edit, setEdit] = useState(false);
|
||||
const [update, setUpdate] = useState(0);
|
||||
const { get } = useApi(getClientAuthToken());
|
||||
const { user } = useUser();
|
||||
const { get } = useApi();
|
||||
const user = useUser();
|
||||
const { data, isLoading, error } = useQuery(['websites', update], () =>
|
||||
get(`/users/${user.id}/websites`),
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import MenuButton from 'components/common/MenuButton';
|
||||
import Gear from 'assets/gear.svg';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DateFilter, { filterOptions } from 'components/common/DateFilter';
|
||||
import { Button } from 'react-basics';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { languages } from 'lib/lang';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import MenuButton from 'components/common/MenuButton';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import { Button } from 'react-basics';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import TimezoneSetting from 'components/settings/TimezoneSetting';
|
||||
import useUser from 'hooks/useUser';
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DateRangeSetting from './DateRangeSetting';
|
||||
import LanguageSetting from './LanguageSetting';
|
||||
@ -8,7 +8,7 @@ import styles from './ProfileSettings.module.css';
|
||||
import ThemeSetting from './ThemeSetting';
|
||||
|
||||
export default function ProfileDetails() {
|
||||
const { user } = useUser();
|
||||
const user = useUser();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import TimezoneSetting from './TimezoneSetting';
|
||||
import DateRangeSetting from './DateRangeSetting';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useTransition, animated } from 'react-spring';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
import Sun from 'assets/sun.svg';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { listTimeZones } from 'timezone-support';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
|
@ -4,7 +4,7 @@ import useUser from 'hooks/useUser';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import { removeItem } from 'next-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button, Icon, Item, Menu, Popup, Text } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import styles from './UserButton.module.css';
|
||||
@ -13,7 +13,7 @@ import useDocumentClick from '../../hooks/useDocumentClick';
|
||||
export default function UserButton() {
|
||||
const [show, setShow] = useState(false);
|
||||
const ref = useRef();
|
||||
const { user } = useUser();
|
||||
const user = useUser();
|
||||
const router = useRouter();
|
||||
const { adminDisabled } = useConfig();
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import { formatDistance } from 'date-fns';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import useApi from 'hooks/useApi';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
@ -26,7 +25,7 @@ const defaultColumns = [
|
||||
|
||||
export default function UsersTable({ columns = defaultColumns, onLoading, onAddKeyClick }) {
|
||||
const [values, setValues] = useState(null);
|
||||
const { get } = useApi(getClientAuthToken());
|
||||
const { get } = useApi();
|
||||
const { data, isLoading, error } = useQuery(['user'], () => get(`/users`));
|
||||
const hasData = data && data.length !== 0;
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { useApi as nextUseApi } from 'next-basics';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import { useRouter } from 'next/router';
|
||||
import * as reactQuery from '@tanstack/react-query';
|
||||
|
||||
export function useApi() {
|
||||
export default function useApi() {
|
||||
const { basePath } = useRouter();
|
||||
|
||||
const { get, post, put, del } = nextUseApi(getClientAuthToken(), basePath);
|
||||
|
||||
return { get, post, put, del };
|
||||
return { get, post, put, del, ...reactQuery };
|
||||
}
|
||||
|
||||
export default useApi;
|
||||
|
@ -1,62 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { saveQuery } from 'store/queries';
|
||||
import useApi from './useApi';
|
||||
|
||||
export default function useFetch(url, options = {}, update = []) {
|
||||
const [response, setResponse] = useState();
|
||||
const [error, setError] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [count, setCount] = useState(0);
|
||||
const { get } = useApi();
|
||||
const { params = {}, headers = {}, disabled = false, delay = 0, interval, onDataLoad } = options;
|
||||
|
||||
async function loadData(params) {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const time = performance.now();
|
||||
|
||||
const { data, status, ok } = await get(url, params, headers);
|
||||
|
||||
await saveQuery(url, { time: performance.now() - time, completed: Date.now() });
|
||||
|
||||
if (status >= 400) {
|
||||
setError(data);
|
||||
setResponse({ data: null, status, ok });
|
||||
} else {
|
||||
setResponse({ data, status, ok });
|
||||
}
|
||||
|
||||
onDataLoad?.(data);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
|
||||
setError(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (url && !disabled) {
|
||||
const id = setTimeout(() => loadData(params), delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}
|
||||
}, [url, disabled, count, ...update]);
|
||||
|
||||
useEffect(() => {
|
||||
if (interval && !disabled) {
|
||||
const id = setInterval(() => setCount(state => state + 1), interval);
|
||||
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
};
|
||||
}
|
||||
}, [interval, disabled]);
|
||||
|
||||
return { ...response, error, loading };
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import useUser from 'hooks/useUser';
|
||||
import useApi from 'hooks/useApi';
|
||||
|
||||
export default function useRequireLogin() {
|
||||
const router = useRouter();
|
||||
const { get } = useApi();
|
||||
const { user, setUser } = useUser();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function loadUser() {
|
||||
setLoading(true);
|
||||
|
||||
const { ok, data } = await get('/auth/verify');
|
||||
|
||||
if (!ok) {
|
||||
await router.push('/login');
|
||||
return null;
|
||||
}
|
||||
|
||||
setUser(data.user);
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loading || user) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadUser();
|
||||
}, [user, loading]);
|
||||
|
||||
return { user, loading };
|
||||
}
|
@ -1,9 +1,33 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import useStore, { setUser } from 'store/app';
|
||||
import useApi from 'hooks/useApi';
|
||||
|
||||
const selector = state => state.user;
|
||||
let loading = false;
|
||||
|
||||
export default function useUser() {
|
||||
const user = useStore(selector);
|
||||
const { get } = useApi();
|
||||
const router = useRouter();
|
||||
|
||||
return { user, setUser };
|
||||
useEffect(() => {
|
||||
async function loadUser() {
|
||||
const { user } = await get('/auth/verify');
|
||||
loading = false;
|
||||
|
||||
if (!user) {
|
||||
await router.push('/login');
|
||||
} else {
|
||||
setUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
if (!user && !loading) {
|
||||
loading = true;
|
||||
loadUser();
|
||||
}
|
||||
}, [user, get, router]);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ function getTimestampInterval(field: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function getJsonField(column, property, isNumber): string {
|
||||
function getJsonField(column: string, property: string, isNumber: boolean): string {
|
||||
const db = getDatabaseType(process.env.DATABASE_URL);
|
||||
|
||||
if (db === POSTGRESQL) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import Layout from 'components/layout/Layout';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -20,7 +20,7 @@ export default async (
|
||||
const { id: websiteId } = req.query;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
if (await canViewWebsite(req.auth, websiteId)) {
|
||||
if (!(await canViewWebsite(req.auth, websiteId))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types';
|
||||
import { canViewWebsite } from 'lib/auth';
|
||||
import { useAuth, useCors } from 'lib/middleware';
|
||||
import moment from 'moment-timezone';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types';
|
||||
import { canViewWebsite } from 'lib/auth';
|
||||
import { useAuth, useCors } from 'lib/middleware';
|
||||
import { getPageviewStats } from 'queries';
|
||||
|
||||
const unitTypes = ['year', 'month', 'hour', 'day'];
|
||||
@ -14,7 +14,7 @@ export interface WebsitePageviewRequestQuery {
|
||||
startAt: number;
|
||||
endAt: number;
|
||||
unit: string;
|
||||
tz: string;
|
||||
timezone: string;
|
||||
url?: string;
|
||||
referrer?: string;
|
||||
os?: string;
|
||||
@ -35,7 +35,7 @@ export default async (
|
||||
startAt,
|
||||
endAt,
|
||||
unit,
|
||||
tz,
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
os,
|
||||
@ -52,7 +52,7 @@ export default async (
|
||||
const startDate = new Date(+startAt);
|
||||
const endDate = new Date(+endAt);
|
||||
|
||||
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
|
||||
if (!moment.tz.zone(timezone) || !unitTypes.includes(unit)) {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ export default async (
|
||||
getPageviewStats(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
timezone: tz,
|
||||
timezone,
|
||||
unit,
|
||||
count: '*',
|
||||
filters: {
|
||||
@ -75,7 +75,7 @@ export default async (
|
||||
getPageviewStats(websiteId, {
|
||||
startDate,
|
||||
endDate,
|
||||
timezone: tz,
|
||||
timezone,
|
||||
unit,
|
||||
count: 'distinct pageview.',
|
||||
filters: {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { canCreateWebsite } from 'lib/auth';
|
||||
import { uuid } from 'lib/crypto';
|
||||
import { useAuth, useCors } from 'lib/middleware';
|
||||
@ -40,7 +39,7 @@ export default async (
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const data: Prisma.WebsiteUncheckedCreateInput = {
|
||||
const data: any = {
|
||||
id: uuid(),
|
||||
name,
|
||||
domain,
|
||||
|
@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import Layout from 'components/layout/Layout';
|
||||
import TestConsole from 'components/pages/TestConsole';
|
||||
import useRequireLogin from 'hooks/useRequireLogin';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { ROLES } from 'lib/constants';
|
||||
|
||||
export default function ConsolePage({ pageDisabled }) {
|
||||
const { loading } = useRequireLogin();
|
||||
const { user } = useUser();
|
||||
const user = useUser();
|
||||
|
||||
if (pageDisabled || loading || user?.role !== ROLES.admin) {
|
||||
if (pageDisabled || !user || !user.isAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Layout from 'components/layout/Layout';
|
||||
import Dashboard from 'components/pages/Dashboard';
|
||||
import useRequireLogin from 'hooks/useRequireLogin';
|
||||
import useUser from 'hooks/useUser';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
|
||||
@ -12,11 +10,10 @@ export default function DashboardPage() {
|
||||
isReady,
|
||||
asPath,
|
||||
} = useRouter();
|
||||
const { loading } = useRequireLogin();
|
||||
const user = useUser();
|
||||
const { adminDisabled } = useConfig();
|
||||
|
||||
if (adminDisabled || !user || !isReady || loading) {
|
||||
if (adminDisabled || !user || !isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import Layout from 'components/layout/Layout';
|
||||
import LoginForm from 'components/forms/LoginForm';
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import Settings from 'components/pages/Settings';
|
||||
import ProfileSettings from 'components/pages/ProfileSettings';
|
||||
import useRequireLogin from 'hooks/useRequireLogin';
|
||||
import React from 'react';
|
||||
import useUser from 'hooks/useUser';
|
||||
|
||||
export default function TeamsPage() {
|
||||
const { loading } = useRequireLogin();
|
||||
const user = useUser();
|
||||
|
||||
if (loading) {
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user