mirror of
https://github.com/umami-software/umami.git
synced 2026-02-09 15:17:23 +01:00
Merge branch 'dev' into master
This commit is contained in:
commit
910a14296b
469 changed files with 7142 additions and 7426 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import BarChartTooltip from 'components/charts/BarChartTooltip';
|
||||
import Chart, { ChartProps } from 'components/charts/Chart';
|
||||
import { useTheme } from 'components/hooks';
|
||||
import { renderNumberLabels } from 'lib/charts';
|
||||
import BarChartTooltip from '@/components/charts/BarChartTooltip';
|
||||
import Chart, { ChartProps } from '@/components/charts/Chart';
|
||||
import { useTheme } from '@/components/hooks';
|
||||
import { renderNumberLabels } from '@/lib/charts';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
export interface BarChartProps extends ChartProps {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useLocale } from 'components/hooks';
|
||||
import { formatDate } from 'lib/date';
|
||||
import { formatLongCurrency, formatLongNumber } from 'lib/format';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { formatDate } from '@/lib/date';
|
||||
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
||||
import { Flexbox, StatusLight } from 'react-basics';
|
||||
|
||||
const formats = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Chart, ChartProps } from 'components/charts/Chart';
|
||||
import { Chart, ChartProps } from '@/components/charts/Chart';
|
||||
import { useState } from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
|
||||
export interface BubbleChartProps extends ChartProps {
|
||||
type?: 'bubble';
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { useState, useRef, useEffect, useMemo, ReactNode } from 'react';
|
|||
import { Loading } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import ChartJS, { LegendItem, ChartOptions } from 'chart.js/auto';
|
||||
import HoverTooltip from 'components/common/HoverTooltip';
|
||||
import Legend from 'components/metrics/Legend';
|
||||
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||
import HoverTooltip from '@/components/common/HoverTooltip';
|
||||
import Legend from '@/components/metrics/Legend';
|
||||
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
|
||||
import styles from './Chart.module.css';
|
||||
|
||||
export interface ChartProps {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Chart, ChartProps } from 'components/charts/Chart';
|
||||
import { Chart, ChartProps } from '@/components/charts/Chart';
|
||||
import { useState } from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
|
||||
export interface PieChartProps extends ChartProps {
|
||||
type?: 'doughnut' | 'pie';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useMemo } from 'react';
|
||||
import { createAvatar } from '@dicebear/core';
|
||||
import { lorelei } from '@dicebear/collection';
|
||||
import { getColor, getPastel } from 'lib/colors';
|
||||
import { getColor, getPastel } from '@/lib/colors';
|
||||
|
||||
const lib = lorelei;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Button, LoadingButton, Form, FormButtons } from 'react-basics';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export interface ConfirmationFormProps {
|
||||
message: ReactNode;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Loading, SearchField } from 'react-basics';
|
||||
import { useMessages, useNavigation } from 'components/hooks';
|
||||
import Empty from 'components/common/Empty';
|
||||
import Pager from 'components/common/Pager';
|
||||
import { PagedQueryResult } from 'lib/types';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import Empty from '@/components/common/Empty';
|
||||
import Pager from '@/components/common/Pager';
|
||||
import { PagedQueryResult } from '@/lib/types';
|
||||
import styles from './DataTable.module.css';
|
||||
import { LoadingPanel } from 'components/common/LoadingPanel';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
|
||||
const DEFAULT_SEARCH_DELAY = 600;
|
||||
|
||||
|
|
@ -37,26 +37,26 @@ export function DataTable({
|
|||
query: { error, isLoading, isFetched },
|
||||
} = queryResult || {};
|
||||
const { page, pageSize, count, data } = result || {};
|
||||
const { query } = params || {};
|
||||
const { search } = params || {};
|
||||
const hasData = Boolean(!isLoading && data?.length);
|
||||
const noResults = Boolean(query && !hasData);
|
||||
const noResults = Boolean(search && !hasData);
|
||||
const { router, renderUrl } = useNavigation();
|
||||
|
||||
const handleSearch = (query: string) => {
|
||||
setParams({ ...params, query, page: params.page ? page : 1 });
|
||||
const handleSearch = (search: string) => {
|
||||
setParams({ ...params, search, page: params.page ? page : 1 });
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setParams({ ...params, query, page });
|
||||
setParams({ ...params, search, page });
|
||||
router.push(renderUrl({ page }));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{allowSearch && (hasData || query) && (
|
||||
{allowSearch && (hasData || search) && (
|
||||
<SearchField
|
||||
className={styles.search}
|
||||
value={query}
|
||||
value={search}
|
||||
onSearch={handleSearch}
|
||||
delay={searchDelay || DEFAULT_SEARCH_DELAY}
|
||||
autoFocus={autoFocus}
|
||||
|
|
@ -71,7 +71,7 @@ export function DataTable({
|
|||
>
|
||||
{hasData ? (typeof children === 'function' ? children(result) : children) : null}
|
||||
{isLoading && <Loading position="page" />}
|
||||
{!isLoading && !hasData && !query && (renderEmpty ? renderEmpty() : <Empty />)}
|
||||
{!isLoading && !hasData && !search && (renderEmpty ? renderEmpty() : <Empty />)}
|
||||
{!isLoading && noResults && <Empty message={formatMessage(messages.noResultsFound)} />}
|
||||
</div>
|
||||
{allowPaging && hasData && (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './Empty.module.css';
|
||||
|
||||
export interface EmptyProps {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon, Text, Flexbox } from 'react-basics';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import Logo from '@/assets/logo.svg';
|
||||
|
||||
export interface EmptyPlaceholderProps {
|
||||
message?: string;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ErrorInfo, ReactNode } from 'react';
|
||||
import { ErrorBoundary as Boundary } from 'react-error-boundary';
|
||||
import { Button } from 'react-basics';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './ErrorBoundary.module.css';
|
||||
|
||||
const logError = (error: Error, info: ErrorInfo) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Icon, Icons, Text } from 'react-basics';
|
||||
import styles from './ErrorMessage.module.css';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function ErrorMessage() {
|
||||
const { formatMessage, messages } = useMessages();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { GROUPED_DOMAINS } from '@/lib/constants';
|
||||
|
||||
function getHostName(url: string) {
|
||||
const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?([^:/\n?=]+)/im);
|
||||
return match && match.length > 1 ? match[1] : null;
|
||||
|
|
@ -9,16 +11,11 @@ export function Favicon({ domain, ...props }) {
|
|||
}
|
||||
|
||||
const hostName = domain ? getHostName(domain) : null;
|
||||
const src = hostName
|
||||
? `https://icons.duckduckgo.com/ip3/${GROUPED_DOMAINS[hostName]?.domain || hostName}.ico`
|
||||
: null;
|
||||
|
||||
return hostName ? (
|
||||
<img
|
||||
src={`https://icons.duckduckgo.com/ip3/${hostName}.ico`}
|
||||
width={16}
|
||||
height={16}
|
||||
alt=""
|
||||
{...props}
|
||||
/>
|
||||
) : null;
|
||||
return hostName ? <img src={src} width={16} height={16} alt="" {...props} /> : null;
|
||||
}
|
||||
|
||||
export default Favicon;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import { useMessages, useNavigation } from 'components/hooks';
|
||||
import { safeDecodeURIComponent } from 'next-basics';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import Link from 'next/link';
|
||||
import { ReactNode } from 'react';
|
||||
import { Icon, Icons } from 'react-basics';
|
||||
|
|
@ -39,7 +38,7 @@ export function FilterLink({
|
|||
{!value && `(${label || formatMessage(labels.unknown)})`}
|
||||
{value && (
|
||||
<Link href={renderUrl({ [id]: value })} className={styles.label} replace>
|
||||
{safeDecodeURIComponent(label || value)}
|
||||
{label || value}
|
||||
</Link>
|
||||
)}
|
||||
{externalUrl && (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import { useLocale } from 'components/hooks';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import styles from './LinkButton.module.css';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface LinkButtonProps {
|
||||
href: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Loading } from 'react-basics';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import Empty from 'components/common/Empty';
|
||||
import ErrorMessage from '@/components/common/ErrorMessage';
|
||||
import Empty from '@/components/common/Empty';
|
||||
import styles from './LoadingPanel.module.css';
|
||||
|
||||
export function LoadingPanel({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import classNames from 'classnames';
|
||||
import { Button, Icon, Icons } from 'react-basics';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './Pager.module.css';
|
||||
|
||||
export interface PagerProps {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
TextField,
|
||||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function TypeConfirmationForm({
|
||||
confirmationValue,
|
||||
|
|
@ -26,7 +26,7 @@ export function TypeConfirmationForm({
|
|||
onConfirm?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
|
||||
if (!confirmationValue) {
|
||||
return null;
|
||||
|
|
@ -35,10 +35,7 @@ export function TypeConfirmationForm({
|
|||
return (
|
||||
<Form onSubmit={onConfirm} error={error}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
{...messages.actionConfirmation}
|
||||
values={{ confirmation: <b>{confirmationValue}</b> }}
|
||||
/>
|
||||
{formatMessage(messages.actionConfirmation, { confirmation: <b>{confirmationValue}</b> })}
|
||||
</p>
|
||||
<FormRow label={formatMessage(labels.confirm)}>
|
||||
<FormInput name="confirm" rules={{ validate: value => value === confirmationValue }}>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
import { useEffect } from 'react';
|
||||
import useStore, { setConfig } from 'store/app';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
let loading = false;
|
||||
import useStore, { setConfig } from '@/store/app';
|
||||
import { getConfig } from '@/app/actions/getConfig';
|
||||
|
||||
export function useConfig() {
|
||||
const { config } = useStore();
|
||||
const { get } = useApi();
|
||||
const configUrl = process.env.configUrl;
|
||||
|
||||
async function loadConfig() {
|
||||
const data = await get(configUrl);
|
||||
loading = false;
|
||||
setConfig(data);
|
||||
setConfig(await getConfig());
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!config && !loading && configUrl) {
|
||||
loading = true;
|
||||
if (!config) {
|
||||
loadConfig();
|
||||
}
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { UseQueryResult } from '@tanstack/react-query';
|
||||
import useStore, { setUser } from 'store/app';
|
||||
import useStore, { setUser } from '@/store/app';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
const selector = (state: { user: any }) => state.user;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useTimezone } from 'components/hooks';
|
||||
import { REALTIME_INTERVAL } from 'lib/constants';
|
||||
import { RealtimeData } from 'lib/types';
|
||||
import { useTimezone } from '@/components/hooks/useTimezone';
|
||||
import { REALTIME_INTERVAL } from '@/lib/constants';
|
||||
import { RealtimeData } from '@/lib/types';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
export function useRealtime(websiteId: string) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||
import { useApi } from '../useApi';
|
||||
import { useTimezone } from '../useTimezone';
|
||||
import { useMessages } from '../useMessages';
|
||||
import { parseDateRange } from '@/lib/date';
|
||||
|
||||
export function useReport(
|
||||
reportId: string,
|
||||
|
|
@ -24,14 +25,12 @@ export function useReport(
|
|||
const data: any = await get(`/reports/${id}`);
|
||||
|
||||
const { dateRange } = data?.parameters || {};
|
||||
const { startDate, endDate } = dateRange || {};
|
||||
|
||||
if (startDate && endDate) {
|
||||
dateRange.startDate = new Date(startDate);
|
||||
dateRange.endDate = new Date(endDate);
|
||||
}
|
||||
|
||||
data.parameters = { ...defaultParameters?.parameters, ...data.parameters };
|
||||
data.parameters = {
|
||||
...defaultParameters?.parameters,
|
||||
...data.parameters,
|
||||
dateRange: parseDateRange(dateRange.value),
|
||||
};
|
||||
|
||||
setReport(data);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function useSessionDataProperties(
|
|||
const params = useFilterParams(websiteId);
|
||||
|
||||
return useQuery<any>({
|
||||
queryKey: ['websites:event-data:properties', { websiteId, ...params }],
|
||||
queryKey: ['websites:session-data:properties', { websiteId, ...params }],
|
||||
queryFn: () => get(`/websites/${websiteId}/session-data/properties`, { ...params }),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import useStore, { setShareToken } from 'store/app';
|
||||
import useStore, { setShareToken } from '@/store/app';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
const selector = (state: { shareToken: string }) => state.shareToken;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export function useTeams(userId: string) {
|
|||
queryFn: (params: any) => {
|
||||
return get(`/users/${userId}/teams`, params);
|
||||
},
|
||||
enabled: !!userId,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useApi } from '../useApi';
|
||||
import { useFilterParams } from '..//useFilterParams';
|
||||
import { useFilterParams } from '../useFilterParams';
|
||||
|
||||
export function useWebsitePageviews(
|
||||
websiteId: string,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import useModified from '../useModified';
|
||||
import { useFilterParams } from 'components/hooks/useFilterParams';
|
||||
import { useFilterParams } from '@/components/hooks/useFilterParams';
|
||||
|
||||
export function useWebsiteSessions(websiteId: string, params?: { [key: string]: string | number }) {
|
||||
const { get } = useApi();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useApi } from '../useApi';
|
||||
import useModified from '../useModified';
|
||||
import { useFilterParams } from 'components/hooks/useFilterParams';
|
||||
import { useFilterParams } from '@/components/hooks/useFilterParams';
|
||||
|
||||
export function useWebsiteSessionsWeekly(
|
||||
websiteId: string,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { useCountryNames, useRegionNames } from 'components/hooks';
|
||||
import { useCountryNames } from '@/components/hooks/useCountryNames';
|
||||
import { useRegionNames } from '@/components/hooks/useRegionNames';
|
||||
import useLocale from '../useLocale';
|
||||
|
||||
export function useWebsiteValues({
|
||||
|
|
|
|||
|
|
@ -1,20 +1,78 @@
|
|||
import { useCallback } from 'react';
|
||||
import * as reactQuery from '@tanstack/react-query';
|
||||
import { useApi as nextUseApi } from 'next-basics';
|
||||
import { getClientAuthToken } from 'lib/client';
|
||||
import { SHARE_TOKEN_HEADER } from 'lib/constants';
|
||||
import useStore from 'store/app';
|
||||
import { getClientAuthToken } from '@/lib/client';
|
||||
import { SHARE_TOKEN_HEADER } from '@/lib/constants';
|
||||
import { httpGet, httpPost, httpPut, httpDelete, FetchResponse } from '@/lib/fetch';
|
||||
import useStore from '@/store/app';
|
||||
|
||||
const selector = (state: { shareToken: { token?: string } }) => state.shareToken;
|
||||
|
||||
async function handleResponse(res: FetchResponse): Promise<any> {
|
||||
if (!res.ok) {
|
||||
return Promise.reject(new Error(res.error));
|
||||
}
|
||||
return Promise.resolve(res.data);
|
||||
}
|
||||
|
||||
function handleError(err: Error | string) {
|
||||
return Promise.reject((err as Error)?.message || err || null);
|
||||
}
|
||||
|
||||
export function useApi() {
|
||||
const shareToken = useStore(selector);
|
||||
|
||||
const { get, post, put, del } = nextUseApi(
|
||||
{ authorization: `Bearer ${getClientAuthToken()}`, [SHARE_TOKEN_HEADER]: shareToken?.token },
|
||||
process.env.basePath,
|
||||
);
|
||||
const defaultHeaders = {
|
||||
authorization: `Bearer ${getClientAuthToken()}`,
|
||||
[SHARE_TOKEN_HEADER]: shareToken?.token,
|
||||
};
|
||||
const basePath = process.env.basePath;
|
||||
|
||||
return { get, post, put, del, ...reactQuery };
|
||||
const getUrl = (url: string) => {
|
||||
return url.startsWith('http') ? url : `${basePath || ''}/api${url}`;
|
||||
};
|
||||
|
||||
const getHeaders = (headers: any = {}) => {
|
||||
return { ...defaultHeaders, ...headers };
|
||||
};
|
||||
|
||||
return {
|
||||
get: useCallback(
|
||||
async (url: string, params: object = {}, headers: object = {}) => {
|
||||
return httpGet(getUrl(url), params, getHeaders(headers))
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
},
|
||||
[httpGet],
|
||||
),
|
||||
|
||||
post: useCallback(
|
||||
async (url: string, params: object = {}, headers: object = {}) => {
|
||||
return httpPost(getUrl(url), params, getHeaders(headers))
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
},
|
||||
[httpPost],
|
||||
),
|
||||
|
||||
put: useCallback(
|
||||
async (url: string, params: object = {}, headers: object = {}) => {
|
||||
return httpPut(getUrl(url), params, getHeaders(headers))
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
},
|
||||
[httpPut],
|
||||
),
|
||||
|
||||
del: useCallback(
|
||||
async (url: string, params: object = {}, headers: object = {}) => {
|
||||
return httpDelete(getUrl(url), params, getHeaders(headers))
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
},
|
||||
[httpDelete],
|
||||
),
|
||||
...reactQuery,
|
||||
};
|
||||
}
|
||||
|
||||
export default useApi;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { httpGet } from 'next-basics';
|
||||
import { httpGet } from '@/lib/fetch';
|
||||
import enUS from '../../../public/intl/country/en-US.json';
|
||||
|
||||
const countryNames = {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { getMinimumUnit, parseDateRange } from 'lib/date';
|
||||
import { setItem } from 'next-basics';
|
||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_COMPARE, DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||
import websiteStore, { setWebsiteDateRange, setWebsiteDateCompare } from 'store/websites';
|
||||
import appStore, { setDateRange } from 'store/app';
|
||||
import { DateRange } from 'lib/types';
|
||||
import { getMinimumUnit, parseDateRange } from '@/lib/date';
|
||||
import { setItem } from '@/lib/storage';
|
||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_COMPARE, DEFAULT_DATE_RANGE } from '@/lib/constants';
|
||||
import websiteStore, { setWebsiteDateRange, setWebsiteDateCompare } from '@/store/websites';
|
||||
import appStore, { setDateRange } from '@/store/app';
|
||||
import { DateRange } from '@/lib/types';
|
||||
import { useLocale } from './useLocale';
|
||||
import { useApi } from './useApi';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useMessages } from './useMessages';
|
||||
import { OPERATORS } from 'lib/constants';
|
||||
import { OPERATORS } from '@/lib/constants';
|
||||
|
||||
export function useFilters() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import useMessages from './useMessages';
|
||||
import { BROWSERS, OS_NAMES } from 'lib/constants';
|
||||
import { BROWSERS, OS_NAMES } from '@/lib/constants';
|
||||
import useLocale from './useLocale';
|
||||
import useCountryNames from './useCountryNames';
|
||||
import useLanguageNames from './useLanguageNames';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { httpGet } from 'next-basics';
|
||||
import { httpGet } from '@/lib/fetch';
|
||||
import enUS from '../../../public/intl/language/en-US.json';
|
||||
|
||||
const languageNames = {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useEffect } from 'react';
|
||||
import { httpGet, setItem } from 'next-basics';
|
||||
import { LOCALE_CONFIG } from 'lib/constants';
|
||||
import { getDateLocale, getTextDirection } from 'lib/lang';
|
||||
import useStore, { setLocale } from 'store/app';
|
||||
import { httpGet } from '@/lib/fetch';
|
||||
import { setItem } from '@/lib/storage';
|
||||
import { LOCALE_CONFIG } from '@/lib/constants';
|
||||
import { getDateLocale, getTextDirection } from '@/lib/lang';
|
||||
import useStore, { setLocale } from '@/store/app';
|
||||
import { useForceUpdate } from './useForceUpdate';
|
||||
import enUS from '../../../public/intl/country/en-US.json';
|
||||
|
||||
|
|
@ -19,13 +20,9 @@ export function useLocale() {
|
|||
const dateLocale = getDateLocale(locale);
|
||||
|
||||
async function loadMessages(locale: string) {
|
||||
const { ok, data } = await httpGet(
|
||||
`${process.env.basePath || ''}/intl/messages/${locale}.json`,
|
||||
);
|
||||
const { data } = await httpGet(`${process.env.basePath || ''}/intl/messages/${locale}.json`);
|
||||
|
||||
if (ok) {
|
||||
messages[locale] = data;
|
||||
}
|
||||
messages[locale] = data;
|
||||
}
|
||||
|
||||
async function saveLocale(value: string) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import { messages, labels } from 'components/messages';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { messages, labels } from '@/components/messages';
|
||||
|
||||
export function useMessages(): any {
|
||||
const intl = useIntl();
|
||||
|
|
@ -21,7 +21,7 @@ export function useMessages(): any {
|
|||
return descriptor ? intl.formatMessage(descriptor, values, opts) : null;
|
||||
};
|
||||
|
||||
return { formatMessage, FormattedMessage, messages, labels, getMessage };
|
||||
return { formatMessage, messages, labels, getMessage };
|
||||
}
|
||||
|
||||
export default useMessages;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import useStore from 'store/modified';
|
||||
import useStore from '@/store/modified';
|
||||
|
||||
export function useModified(key?: string) {
|
||||
const modified = useStore(state => state?.[key]);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useMemo } from 'react';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { buildUrl, safeDecodeURIComponent } from 'next-basics';
|
||||
import { buildUrl } from '@/lib/url';
|
||||
|
||||
export function useNavigation(): {
|
||||
pathname: string;
|
||||
|
|
@ -16,7 +16,7 @@ export function useNavigation(): {
|
|||
const obj = {};
|
||||
|
||||
for (const [key, value] of params.entries()) {
|
||||
obj[key] = safeDecodeURIComponent(value);
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { PageResult, PageParams, PagedQueryResult } from 'lib/types';
|
||||
import { PageResult, PageParams, PagedQueryResult } from '@/lib/types';
|
||||
import { useApi } from './useApi';
|
||||
import { useNavigation } from './useNavigation';
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ export function usePagedQuery<T = any>({
|
|||
}: Omit<UseQueryOptions, 'queryFn'> & { queryFn: (params?: object) => any }): PagedQueryResult<T> {
|
||||
const { query: queryParams } = useNavigation();
|
||||
const [params, setParams] = useState<PageParams>({
|
||||
query: '',
|
||||
search: '',
|
||||
page: +queryParams.page || 1,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import useStore, { setTheme } from 'store/app';
|
||||
import { getItem, setItem } from 'next-basics';
|
||||
import { DEFAULT_THEME, THEME_COLORS, THEME_CONFIG } from 'lib/constants';
|
||||
import useStore, { setTheme } from '@/store/app';
|
||||
import { getItem, setItem } from '@/lib/storage';
|
||||
import { DEFAULT_THEME, THEME_COLORS, THEME_CONFIG } from '@/lib/constants';
|
||||
import { colord } from 'colord';
|
||||
|
||||
const selector = (state: { theme: string }) => state.theme;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { setItem } from 'next-basics';
|
||||
import { TIMEZONE_CONFIG } from 'lib/constants';
|
||||
import { setItem } from '@/lib/storage';
|
||||
import { TIMEZONE_CONFIG } from '@/lib/constants';
|
||||
import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
|
||||
import useStore, { setTimezone } from 'store/app';
|
||||
import useLocale from './useLocale';
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
import { Icons } from 'react-basics';
|
||||
import AddUser from 'assets/add-user.svg';
|
||||
import Bars from 'assets/bars.svg';
|
||||
import BarChart from 'assets/bar-chart.svg';
|
||||
import Bolt from 'assets/bolt.svg';
|
||||
import Calendar from 'assets/calendar.svg';
|
||||
import Change from 'assets/change.svg';
|
||||
import Clock from 'assets/clock.svg';
|
||||
import Compare from 'assets/compare.svg';
|
||||
import Dashboard from 'assets/dashboard.svg';
|
||||
import Eye from 'assets/eye.svg';
|
||||
import Gear from 'assets/gear.svg';
|
||||
import Globe from 'assets/globe.svg';
|
||||
import Location from 'assets/location.svg';
|
||||
import Lock from 'assets/lock.svg';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import Magnet from 'assets/magnet.svg';
|
||||
import Moon from 'assets/moon.svg';
|
||||
import Nodes from 'assets/nodes.svg';
|
||||
import Overview from 'assets/overview.svg';
|
||||
import Profile from 'assets/profile.svg';
|
||||
import PushPin from 'assets/pushpin.svg';
|
||||
import Reports from 'assets/reports.svg';
|
||||
import Sun from 'assets/sun.svg';
|
||||
import User from 'assets/user.svg';
|
||||
import Users from 'assets/users.svg';
|
||||
import Visitor from 'assets/visitor.svg';
|
||||
import AddUser from '@/assets/add-user.svg';
|
||||
import Bars from '@/assets/bars.svg';
|
||||
import BarChart from '@/assets/bar-chart.svg';
|
||||
import Bolt from '@/assets/bolt.svg';
|
||||
import Calendar from '@/assets/calendar.svg';
|
||||
import Change from '@/assets/change.svg';
|
||||
import Clock from '@/assets/clock.svg';
|
||||
import Compare from '@/assets/compare.svg';
|
||||
import Dashboard from '@/assets/dashboard.svg';
|
||||
import Eye from '@/assets/eye.svg';
|
||||
import Gear from '@/assets/gear.svg';
|
||||
import Globe from '@/assets/globe.svg';
|
||||
import Location from '@/assets/location.svg';
|
||||
import Lock from '@/assets/lock.svg';
|
||||
import Logo from '@/assets/logo.svg';
|
||||
import Magnet from '@/assets/magnet.svg';
|
||||
import Moon from '@/assets/moon.svg';
|
||||
import Nodes from '@/assets/nodes.svg';
|
||||
import Overview from '@/assets/overview.svg';
|
||||
import Profile from '@/assets/profile.svg';
|
||||
import PushPin from '@/assets/pushpin.svg';
|
||||
import Reports from '@/assets/reports.svg';
|
||||
import Sun from '@/assets/sun.svg';
|
||||
import User from '@/assets/user.svg';
|
||||
import Users from '@/assets/users.svg';
|
||||
import Visitor from '@/assets/visitor.svg';
|
||||
|
||||
const icons = {
|
||||
...Icons,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useState } from 'react';
|
||||
import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
|
||||
import { endOfYear, isSameDay } from 'date-fns';
|
||||
import DatePickerForm from 'components/metrics/DatePickerForm';
|
||||
import { useLocale, useMessages } from 'components/hooks';
|
||||
import Icons from 'components/icons';
|
||||
import { formatDate, parseDateValue } from 'lib/date';
|
||||
import DatePickerForm from '@/components/metrics/DatePickerForm';
|
||||
import { useLocale, useMessages } from '@/components/hooks';
|
||||
import Icons from '@/components/icons';
|
||||
import { formatDate, parseDateValue } from '@/lib/date';
|
||||
import styles from './DateFilter.module.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Icon, Button, PopupTrigger, Popup } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { languages } from 'lib/lang';
|
||||
import { useLocale } from 'components/hooks';
|
||||
import Icons from 'components/icons';
|
||||
import { languages } from '@/lib/lang';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import Icons from '@/components/icons';
|
||||
import styles from './LanguageButton.module.css';
|
||||
|
||||
export function LanguageButton() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button, Icon, Icons, TooltipPopup } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function LogoutButton({
|
||||
tooltipPosition = 'top',
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import {
|
|||
Popup,
|
||||
} from 'react-basics';
|
||||
import { startOfMonth, endOfMonth } from 'date-fns';
|
||||
import Icons from 'components/icons';
|
||||
import { useLocale } from 'components/hooks';
|
||||
import { formatDate } from 'lib/date';
|
||||
import Icons from '@/components/icons';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { formatDate } from '@/lib/date';
|
||||
import styles from './MonthSelect.module.css';
|
||||
|
||||
export function MonthSelect({ date = new Date(), onChange }) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Key } from 'react';
|
||||
import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Icons from 'components/icons';
|
||||
import { useMessages, useLogin, useLocale } from 'components/hooks';
|
||||
import { CURRENT_VERSION } from 'lib/constants';
|
||||
import Icons from '@/components/icons';
|
||||
import { useMessages, useLogin, useLocale } from '@/components/hooks';
|
||||
import { CURRENT_VERSION } from '@/lib/constants';
|
||||
import styles from './ProfileButton.module.css';
|
||||
|
||||
export function ProfileButton() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { LoadingButton, Icon, TooltipPopup } from 'react-basics';
|
||||
import { setWebsiteDateRange } from 'store/websites';
|
||||
import { useDateRange } from 'components/hooks';
|
||||
import Icons from 'components/icons';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { setWebsiteDateRange } from '@/store/websites';
|
||||
import { useDateRange } from '@/components/hooks';
|
||||
import Icons from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function RefreshButton({
|
||||
websiteId,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Button, Icon, PopupTrigger, Popup, Form, FormRow } from 'react-basics';
|
||||
import TimezoneSetting from 'app/(main)/profile/TimezoneSetting';
|
||||
import DateRangeSetting from 'app/(main)/profile/DateRangeSetting';
|
||||
import Icons from 'components/icons';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import TimezoneSetting from '@/app/(main)/profile/TimezoneSetting';
|
||||
import DateRangeSetting from '@/app/(main)/profile/DateRangeSetting';
|
||||
import Icons from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './SettingsButton.module.css';
|
||||
|
||||
export function SettingsButton() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Key } from 'react';
|
||||
import { Text, Icon, Button, Popup, Menu, Item, PopupTrigger, Flexbox } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import Icons from 'components/icons';
|
||||
import { useLogin, useMessages, useTeams, useTeamUrl } from 'components/hooks';
|
||||
import Icons from '@/components/icons';
|
||||
import { useLogin, useMessages, useTeams, useTeamUrl } from '@/components/hooks';
|
||||
import styles from './TeamsButton.module.css';
|
||||
|
||||
export function TeamsButton({
|
||||
|
|
@ -16,7 +16,7 @@ export function TeamsButton({
|
|||
}) {
|
||||
const { user } = useLogin();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { result } = useTeams(user?.id);
|
||||
const { result } = useTeams(user.id);
|
||||
const { teamId } = useTeamUrl();
|
||||
const team = result?.data?.find(({ id }) => id === teamId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useTransition, animated } from '@react-spring/web';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
import { useTheme } from 'components/hooks';
|
||||
import Icons from 'components/icons';
|
||||
import { useTheme } from '@/components/hooks';
|
||||
import Icons from '@/components/icons';
|
||||
import styles from './ThemeButton.module.css';
|
||||
|
||||
export function ThemeButton() {
|
||||
|
|
@ -28,7 +28,7 @@ export function ThemeButton() {
|
|||
<Button variant="quiet" className={styles.button} onClick={handleClick}>
|
||||
{transitions((style, item) => (
|
||||
<animated.div key={item} style={style}>
|
||||
<Icon className={styles.icon}>{item === 'light' ? <Icons.Sun /> : <Icons.Moon />}</Icon>
|
||||
<Icon>{item === 'light' ? <Icons.Sun /> : <Icons.Moon />}</Icon>
|
||||
</animated.div>
|
||||
))}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useDateRange, useLocale } from 'components/hooks';
|
||||
import { useDateRange, useLocale } from '@/components/hooks';
|
||||
import { isAfter } from 'date-fns';
|
||||
import { getOffsetDateRange } from 'lib/date';
|
||||
import { getOffsetDateRange } from '@/lib/date';
|
||||
import { Button, Icon, Icons } from 'react-basics';
|
||||
import DateFilter from './DateFilter';
|
||||
import styles from './WebsiteDateFilter.module.css';
|
||||
import { DateRange } from 'lib/types';
|
||||
import { DateRange } from '@/lib/types';
|
||||
|
||||
export function WebsiteDateFilter({
|
||||
websiteId,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, Key } from 'react';
|
||||
import { Dropdown, Item } from 'react-basics';
|
||||
import { useWebsite, useWebsites, useMessages } from 'components/hooks';
|
||||
import Empty from 'components/common/Empty';
|
||||
import { useWebsite, useWebsites, useMessages } from '@/components/hooks';
|
||||
import Empty from '@/components/common/Empty';
|
||||
import styles from './WebsiteSelect.module.css';
|
||||
|
||||
export function WebsiteSelect({
|
||||
|
|
@ -14,12 +14,12 @@ export function WebsiteSelect({
|
|||
onSelect?: (key: any) => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const [query, setQuery] = useState('');
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedId, setSelectedId] = useState<Key>(websiteId);
|
||||
|
||||
const { data: website } = useWebsite(selectedId as string);
|
||||
|
||||
const queryResult = useWebsites({ teamId }, { query, pageSize: 5 });
|
||||
const queryResult = useWebsites({ teamId }, { search, pageSize: 5 });
|
||||
|
||||
const renderValue = () => {
|
||||
return website?.name;
|
||||
|
|
@ -35,7 +35,7 @@ export function WebsiteSelect({
|
|||
};
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setQuery(value);
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import SideNav from 'components/layout/SideNav';
|
||||
import SideNav from '@/components/layout/SideNav';
|
||||
import styles from './MenuLayout.module.css';
|
||||
|
||||
export function MenuLayout({ items = [], children }: { items: any[]; children: ReactNode }) {
|
||||
|
|
|
|||
|
|
@ -51,11 +51,6 @@ a.item {
|
|||
color: var(--base900);
|
||||
}
|
||||
|
||||
.item.disabled {
|
||||
color: var(--base500) !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.minimized .text,
|
||||
.minimized .header {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Icon, Text, TooltipPopup } from 'react-basics';
|
|||
import classNames from 'classnames';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import Icons from 'components/icons';
|
||||
import Icons from '@/components/icons';
|
||||
import styles from './NavGroup.module.css';
|
||||
|
||||
export interface NavGroupProps {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Banner, Loading } from 'react-basics';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './Page.module.css';
|
||||
|
||||
export function Page({
|
||||
|
|
|
|||
|
|
@ -280,6 +280,23 @@ export const labels = defineMessages({
|
|||
lastSeen: { id: 'label.last-seen', defaultMessage: 'Last seen' },
|
||||
firstSeen: { id: 'label.first-seen', defaultMessage: 'First seen' },
|
||||
properties: { id: 'label.properties', defaultMessage: 'Properties' },
|
||||
channels: { id: 'label.channels', defaultMessage: 'Channels' },
|
||||
direct: { id: 'label.direct', defaultMessage: 'Direct' },
|
||||
referral: { id: 'label.referral', defaultMessage: 'Referral' },
|
||||
affiliate: { id: 'label.affiliate', defaultMessage: 'Affiliate' },
|
||||
email: { id: 'label.email', defaultMessage: 'Email' },
|
||||
sms: { id: 'label.sms', defaultMessage: 'SMS' },
|
||||
organicSearch: { id: 'label.organic-search', defaultMessage: 'Organic search' },
|
||||
organicSocial: { id: 'label.organic-social', defaultMessage: 'Organic social' },
|
||||
organicShopping: { id: 'label.organic-shopping', defaultMessage: 'Organic shopping' },
|
||||
organicVideo: { id: 'label.organic-video', defaultMessage: 'Organic video' },
|
||||
paidAds: { id: 'label.paid-ads', defaultMessage: 'Paid ads' },
|
||||
paidSearch: { id: 'label.paid-search', defaultMessage: 'Paid search' },
|
||||
paidSocial: { id: 'label.paid-social', defaultMessage: 'Paid social' },
|
||||
paidShopping: { id: 'label.paid-shopping', defaultMessage: 'Paid shopping' },
|
||||
paidVideo: { id: 'label.paid-video', defaultMessage: 'Paid video' },
|
||||
grouped: { id: 'label.grouped', defaultMessage: 'Grouped' },
|
||||
other: { id: 'label.other', defaultMessage: 'Other' },
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
|
|
|
|||
|
|
@ -10,8 +10,3 @@
|
|||
font-size: var(--font-size-md);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 600;
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useMemo } from 'react';
|
||||
import { StatusLight } from 'react-basics';
|
||||
import { useApi } from 'components/hooks';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useApi } from '@/components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './ActiveUsers.module.css';
|
||||
|
||||
export function ActiveUsers({
|
||||
|
|
@ -24,7 +24,7 @@ export function ActiveUsers({
|
|||
|
||||
const count = useMemo(() => {
|
||||
if (websiteId) {
|
||||
return data?.x || 0;
|
||||
return data?.visitors || 0;
|
||||
}
|
||||
|
||||
return value !== undefined ? value : 0;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import FilterLink from 'components/common/FilterLink';
|
||||
import MetricsTable, { MetricsTableProps } from 'components/metrics/MetricsTable';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useFormat } from 'components/hooks';
|
||||
import TypeIcon from 'components/common/TypeIcon';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import MetricsTable, { MetricsTableProps } from '@/components/metrics/MetricsTable';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { useFormat } from '@/components/hooks';
|
||||
import TypeIcon from '@/components/common/TypeIcon';
|
||||
|
||||
export function BrowsersTable(props: MetricsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
22
src/components/metrics/ChannelsTable.tsx
Normal file
22
src/components/metrics/ChannelsTable.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import MetricsTable, { MetricsTableProps } from '@/components/metrics/MetricsTable';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function ChannelsTable(props: MetricsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const renderLabel = ({ x }) => {
|
||||
return formatMessage(labels[x]);
|
||||
};
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={formatMessage(labels.channels)}
|
||||
type="channel"
|
||||
renderLabel={renderLabel}
|
||||
metric={formatMessage(labels.visitors)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelsTable;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import { emptyFilter } from 'lib/filters';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useFormat } from 'components/hooks';
|
||||
import { emptyFilter } from '@/lib/filters';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { useFormat } from '@/components/hooks';
|
||||
|
||||
export function CitiesTable(props: MetricsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import FilterLink from 'components/common/FilterLink';
|
||||
import { useCountryNames } from 'components/hooks';
|
||||
import { useLocale, useMessages, useFormat } from 'components/hooks';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { useCountryNames } from '@/components/hooks';
|
||||
import { useLocale, useMessages, useFormat } from '@/components/hooks';
|
||||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import TypeIcon from 'components/common/TypeIcon';
|
||||
import TypeIcon from '@/components/common/TypeIcon';
|
||||
|
||||
export function CountriesTable({ ...props }: MetricsTableProps) {
|
||||
const { locale } = useLocale();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, ButtonGroup, Calendar } from 'react-basics';
|
||||
import { isAfter, isBefore, isSameDay, startOfDay, endOfDay } from 'date-fns';
|
||||
import { useLocale } from 'components/hooks';
|
||||
import { FILTER_DAY, FILTER_RANGE } from 'lib/constants';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { FILTER_DAY, FILTER_RANGE } from '@/lib/constants';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './DatePickerForm.module.css';
|
||||
|
||||
export function DatePickerForm({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useFormat } from 'components/hooks';
|
||||
import TypeIcon from 'components/common/TypeIcon';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { useFormat } from '@/components/hooks';
|
||||
import TypeIcon from '@/components/common/TypeIcon';
|
||||
|
||||
export function DevicesTable(props: MetricsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { colord } from 'colord';
|
||||
import BarChart from 'components/charts/BarChart';
|
||||
import { useDateRange, useLocale, useWebsiteEventsSeries } from 'components/hooks';
|
||||
import { renderDateLabels } from 'lib/charts';
|
||||
import { CHART_COLORS } from 'lib/constants';
|
||||
import BarChart from '@/components/charts/BarChart';
|
||||
import { useDateRange, useLocale, useWebsiteEventsSeries } from '@/components/hooks';
|
||||
import { renderDateLabels } from '@/lib/charts';
|
||||
import { CHART_COLORS } from '@/lib/constants';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface EventsChartProps {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function EventsTable(props: MetricsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ import {
|
|||
useMessages,
|
||||
useFormat,
|
||||
useFilters,
|
||||
} from 'components/hooks';
|
||||
import PopupForm from 'app/(main)/reports/[reportId]/PopupForm';
|
||||
import FieldFilterEditForm from 'app/(main)/reports/[reportId]/FieldFilterEditForm';
|
||||
import { OPERATOR_PREFIXES } from 'lib/constants';
|
||||
import { isSearchOperator, parseParameterValue } from 'lib/params';
|
||||
} from '@/components/hooks';
|
||||
import PopupForm from '@/app/(main)/reports/[reportId]/PopupForm';
|
||||
import FieldFilterEditForm from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
|
||||
import { OPERATOR_PREFIXES } from '@/lib/constants';
|
||||
import { isSearchOperator, parseParameterValue } from '@/lib/params';
|
||||
import styles from './FilterTags.module.css';
|
||||
import WebsiteFilterButton from 'app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||
import WebsiteFilterButton from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||
|
||||
export function FilterTags({
|
||||
websiteId,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Flexbox } from 'react-basics';
|
||||
|
||||
export function HostsTable(props: MetricsTableProps) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { useLocale } from 'components/hooks';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useFormat } from 'components/hooks';
|
||||
import { percentFilter } from '@/lib/filters';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { useFormat } from '@/components/hooks';
|
||||
|
||||
export function LanguagesTable({
|
||||
onDataLoad,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { StatusLight } from 'react-basics';
|
||||
import { safeDecodeURIComponent } from 'next-basics';
|
||||
import { colord } from 'colord';
|
||||
import classNames from 'classnames';
|
||||
import { LegendItem } from 'chart.js/auto';
|
||||
|
|
@ -28,9 +27,7 @@ export function Legend({
|
|||
className={classNames(styles.label, { [styles.hidden]: hidden })}
|
||||
onClick={() => onClick(item)}
|
||||
>
|
||||
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()}>
|
||||
{safeDecodeURIComponent(text)}
|
||||
</StatusLight>
|
||||
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()}>{text}</StatusLight>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { FixedSizeList } from 'react-window';
|
||||
import { useSpring, animated, config } from '@react-spring/web';
|
||||
import classNames from 'classnames';
|
||||
import Empty from 'components/common/Empty';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import Empty from '@/components/common/Empty';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './ListTable.module.css';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import classNames from 'classnames';
|
||||
import { useSpring, animated } from '@react-spring/web';
|
||||
import { formatNumber } from 'lib/format';
|
||||
import ChangeLabel from 'components/metrics/ChangeLabel';
|
||||
import { formatNumber } from '@/lib/format';
|
||||
import ChangeLabel from '@/components/metrics/ChangeLabel';
|
||||
import styles from './MetricCard.module.css';
|
||||
|
||||
export interface MetricCardProps {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Loading, cloneChildren } from 'react-basics';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import ErrorMessage from '@/components/common/ErrorMessage';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import styles from './MetricsBar.module.css';
|
||||
|
||||
export interface MetricsBarProps {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { Loading, Icon, Text, SearchField } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import ErrorMessage from 'components/common/ErrorMessage';
|
||||
import LinkButton from 'components/common/LinkButton';
|
||||
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import ErrorMessage from '@/components/common/ErrorMessage';
|
||||
import LinkButton from '@/components/common/LinkButton';
|
||||
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
|
||||
import { percentFilter } from '@/lib/filters';
|
||||
import {
|
||||
useNavigation,
|
||||
useWebsiteMetrics,
|
||||
useMessages,
|
||||
useLocale,
|
||||
useFormat,
|
||||
} from 'components/hooks';
|
||||
import Icons from 'components/icons';
|
||||
} from '@/components/hooks';
|
||||
import Icons from '@/components/icons';
|
||||
import ListTable, { ListTableProps } from './ListTable';
|
||||
import styles from './MetricsTable.module.css';
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ export function MetricsTable({
|
|||
return filter(arr);
|
||||
}, items);
|
||||
} else {
|
||||
items = dataFilter(data);
|
||||
items = dataFilter(items);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { useMessages, useFormat } from 'components/hooks';
|
||||
import TypeIcon from 'components/common/TypeIcon';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { useMessages, useFormat } from '@/components/hooks';
|
||||
import TypeIcon from '@/components/common/TypeIcon';
|
||||
|
||||
export function OSTable(props: MetricsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { WebsiteContext } from 'app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { useMessages, useNavigation } from 'components/hooks';
|
||||
import { emptyFilter } from 'lib/filters';
|
||||
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||
import FilterButtons from '@/components/common/FilterButtons';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { emptyFilter } from '@/lib/filters';
|
||||
import { useContext } from 'react';
|
||||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useMemo } from 'react';
|
||||
import BarChart, { BarChartProps } from 'components/charts/BarChart';
|
||||
import { useLocale, useTheme, useMessages } from 'components/hooks';
|
||||
import { renderDateLabels } from 'lib/charts';
|
||||
import BarChart, { BarChartProps } from '@/components/charts/BarChart';
|
||||
import { useLocale, useTheme, useMessages } from '@/components/hooks';
|
||||
import { renderDateLabels } from '@/lib/charts';
|
||||
|
||||
export interface PagepageviewsChartProps extends BarChartProps {
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { useState } from 'react';
|
||||
import { safeDecodeURI } from 'next-basics';
|
||||
import FilterButtons from 'components/common/FilterButtons';
|
||||
import { emptyFilter, paramFilter } from 'lib/filters';
|
||||
import { FILTER_RAW, FILTER_COMBINED } from 'lib/constants';
|
||||
import FilterButtons from '@/components/common/FilterButtons';
|
||||
import { emptyFilter, paramFilter } from '@/lib/filters';
|
||||
import { FILTER_RAW, FILTER_COMBINED } from '@/lib/constants';
|
||||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './QueryParametersTable.module.css';
|
||||
|
||||
const filters = {
|
||||
|
|
@ -39,8 +38,8 @@ export function QueryParametersTable({
|
|||
x
|
||||
) : (
|
||||
<div className={styles.item}>
|
||||
<div className={styles.param}>{safeDecodeURI(p)}</div>
|
||||
<div className={styles.value}>{safeDecodeURI(v)}</div>
|
||||
<div className={styles.param}>{p}</div>
|
||||
<div className={styles.value}>{v}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useMemo, useRef } from 'react';
|
||||
import { startOfMinute, subMinutes, isBefore } from 'date-fns';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants';
|
||||
import { RealtimeData } from 'lib/types';
|
||||
import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from '@/lib/constants';
|
||||
import { RealtimeData } from '@/lib/types';
|
||||
|
||||
export interface RealtimeChartProps {
|
||||
data: RealtimeData;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,53 @@
|
|||
import FilterLink from 'components/common/FilterLink';
|
||||
import Favicon from 'components/common/Favicon';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import Favicon from '@/components/common/Favicon';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import FilterButtons from '@/components/common/FilterButtons';
|
||||
import thenby from 'thenby';
|
||||
import { GROUPED_DOMAINS } from '@/lib/constants';
|
||||
import { Flexbox } from 'react-basics';
|
||||
|
||||
export function ReferrersTable(props: MetricsTableProps) {
|
||||
export interface ReferrersTableProps extends MetricsTableProps {
|
||||
allowFilter?: boolean;
|
||||
}
|
||||
|
||||
export function ReferrersTable({ allowFilter, ...props }: ReferrersTableProps) {
|
||||
const {
|
||||
router,
|
||||
renderUrl,
|
||||
query: { view = 'referrer' },
|
||||
} = useNavigation();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleSelect = (key: any) => {
|
||||
router.push(renderUrl({ view: key }), { scroll: false });
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: formatMessage(labels.domain),
|
||||
key: 'referrer',
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.grouped),
|
||||
key: 'grouped',
|
||||
},
|
||||
];
|
||||
|
||||
const renderLink = ({ x: referrer }) => {
|
||||
if (view === 'grouped') {
|
||||
if (referrer === '_other') {
|
||||
return `(${formatMessage(labels.other)})`;
|
||||
} else {
|
||||
return (
|
||||
<Flexbox alignItems="center" gap={10}>
|
||||
<Favicon domain={referrer} />
|
||||
{GROUPED_DOMAINS.find(({ domain }) => domain === referrer)?.name}
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FilterLink
|
||||
id="referrer"
|
||||
|
|
@ -19,15 +60,41 @@ export function ReferrersTable(props: MetricsTableProps) {
|
|||
);
|
||||
};
|
||||
|
||||
const groupedFilter = (data: any[]) => {
|
||||
const groups = { _other: 0 };
|
||||
|
||||
for (const { x, y } of data) {
|
||||
for (const { domain, match } of GROUPED_DOMAINS) {
|
||||
if (Array.isArray(match) ? match.some(str => x.includes(str)) : x.includes(match)) {
|
||||
if (!groups[domain]) {
|
||||
groups[domain] = 0;
|
||||
}
|
||||
groups[domain] += y;
|
||||
} else {
|
||||
groups._other += y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(groups)
|
||||
.map((key: any) => ({ x: key, y: groups[key] }))
|
||||
.sort(thenby.firstBy('y', -1));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MetricsTable
|
||||
{...props}
|
||||
title={formatMessage(labels.referrers)}
|
||||
type="referrer"
|
||||
metric={formatMessage(labels.views)}
|
||||
metric={formatMessage(labels.visitors)}
|
||||
dataFilter={view === 'grouped' ? groupedFilter : undefined}
|
||||
renderLabel={renderLink}
|
||||
/>
|
||||
>
|
||||
{allowFilter && (
|
||||
<FilterButtons items={buttons} selectedKey={view} onSelect={handleSelect} />
|
||||
)}
|
||||
</MetricsTable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import FilterLink from 'components/common/FilterLink';
|
||||
import { emptyFilter } from 'lib/filters';
|
||||
import { useMessages, useLocale, useRegionNames } from 'components/hooks';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { emptyFilter } from '@/lib/filters';
|
||||
import { useMessages, useLocale, useRegionNames } from '@/components/hooks';
|
||||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import TypeIcon from 'components/common/TypeIcon';
|
||||
import TypeIcon from '@/components/common/TypeIcon';
|
||||
|
||||
export function RegionsTable(props: MetricsTableProps) {
|
||||
const { locale } = useLocale();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function ScreenTable(props: MetricsTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import MetricsTable, { MetricsTableProps } from './MetricsTable';
|
||||
import FilterLink from 'components/common/FilterLink';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import FilterLink from '@/components/common/FilterLink';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Flexbox } from 'react-basics';
|
||||
|
||||
export function TagsTable(props: MetricsTableProps) {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import { useState, useMemo, HTMLAttributes } from 'react';
|
|||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import classNames from 'classnames';
|
||||
import { colord } from 'colord';
|
||||
import HoverTooltip from 'components/common/HoverTooltip';
|
||||
import { ISO_COUNTRIES, MAP_FILE } from 'lib/constants';
|
||||
import { useDateRange, useTheme, useWebsiteMetrics } from 'components/hooks';
|
||||
import { useCountryNames } from 'components/hooks';
|
||||
import { useLocale } from 'components/hooks';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import HoverTooltip from '@/components/common/HoverTooltip';
|
||||
import { ISO_COUNTRIES, MAP_FILE } from '@/lib/constants';
|
||||
import { useDateRange, useTheme, useWebsiteMetrics } from '@/components/hooks';
|
||||
import { useCountryNames } from '@/components/hooks';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import { percentFilter } from '@/lib/filters';
|
||||
import styles from './WorldMap.module.css';
|
||||
|
||||
export function WorldMap({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue