Refactored fetching to use react-query.

This commit is contained in:
Mike Cao 2022-12-28 15:43:22 -08:00
parent 7bbed0e12b
commit c56f02c475
112 changed files with 255 additions and 492 deletions

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import classNames from 'classnames';
import {
startOfWeek,

View File

@ -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';

View File

@ -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';

View File

@ -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;

View File

@ -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;
}

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './Favicon.module.css';

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import ButtonLayout from 'components/layout/ButtonLayout';
import { ButtonGroup } from 'react-basics';

View File

@ -1,4 +1,3 @@
import React from 'react';
import classNames from 'classnames';
import Link from 'next/link';
import { safeDecodeURI } from 'next-basics';

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styles from './Icon.module.css';

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import NextLink from 'next/link';

View File

@ -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;

View File

@ -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;
}

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styles from './Menu.module.css';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useRouter } from 'next/router';
import classNames from 'classnames';

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';

View File

@ -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';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styles from './Tag.module.css';

View File

@ -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';

View File

@ -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';

View File

@ -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 }),
);

View File

@ -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);

View File

@ -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);

View File

@ -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 => {

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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 => {

View File

@ -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),
);

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from 'react';
import { useState, useRef, useEffect } from 'react';
function isInViewport(element) {
const rect = element.getBoundingClientRect();

View File

@ -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({

View File

@ -1,4 +1,3 @@
import React from 'react';
import classNames from 'classnames';
import styles from './ButtonLayout.module.css';

View File

@ -1,4 +1,3 @@
import React from 'react';
import { useSpring, animated } from 'react-spring';
import classNames from 'classnames';
import { ErrorMessage } from 'formik';

View File

@ -1,4 +1,3 @@
import React from 'react';
import classNames from 'classnames';
import styles from './GridLayout.module.css';

View File

@ -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/');

View File

@ -1,4 +1,3 @@
import React from 'react';
import { useRouter } from 'next/router';
import classNames from 'classnames';
import NavMenu from 'components/common/NavMenu';

View File

@ -1,4 +1,3 @@
import React from 'react';
import Link from 'next/link';
import classNames from 'classnames';
import { Button, Icon } from 'react-basics';

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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>

View File

@ -1,4 +1,3 @@
import React from 'react';
import MetricsTable from './MetricsTable';
import { percentFilter } from 'lib/filters';
import { useIntl, defineMessages } from 'react-intl';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import MetricsTable from './MetricsTable';
import { useIntl, FormattedMessage } from 'react-intl';
import { getDeviceMessage } from 'components/messages';

View File

@ -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
/>
);

View File

@ -1,4 +1,3 @@
import React from 'react';
import classNames from 'classnames';
import { safeDecodeURI } from 'next-basics';
import { Button } from 'react-basics';

View File

@ -1,4 +1,3 @@
import React from 'react';
import MetricsTable from './MetricsTable';
import { percentFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl';

View File

@ -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>
);

View File

@ -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';

View File

@ -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 && (
<>

View File

@ -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}>

View File

@ -1,4 +1,3 @@
import React from 'react';
import MetricsTable from './MetricsTable';
import { FormattedMessage } from 'react-intl';
import FilterLink from 'components/common/FilterLink';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import { useIntl } from 'react-intl';
import { colord } from 'colord';
import CheckVisible from 'components/helpers/CheckVisible';

View File

@ -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';

View File

@ -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';

View File

@ -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}>

View File

@ -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';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import MetricsTable from './MetricsTable';
import { FormattedMessage } from 'react-intl';

View File

@ -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>

View File

@ -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' },
];

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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],

View File

@ -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();

View File

@ -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;
}

View File

@ -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(

View File

@ -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 = () => {

View File

@ -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>

View File

@ -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],

View File

@ -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`),
);

View File

@ -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';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import { languages } from 'lib/lang';
import useLocale from 'hooks/useLocale';
import MenuButton from 'components/common/MenuButton';

View File

@ -1,4 +1,3 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import DropDown from 'components/common/DropDown';
import { Button } from 'react-basics';

View File

@ -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;

View File

@ -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';

View File

@ -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';

View File

@ -1,4 +1,3 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { listTimeZones } from 'timezone-support';
import DropDown from 'components/common/DropDown';

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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 };
}

View File

@ -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 };
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -1,4 +1,3 @@
import React from 'react';
import Layout from 'components/layout/Layout';
import { FormattedMessage } from 'react-intl';

View File

@ -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);
}

View File

@ -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: {

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,4 +1,3 @@
import React from 'react';
import Layout from 'components/layout/Layout';
import LoginForm from 'components/forms/LoginForm';

View File

@ -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