Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Brian Cao 2023-09-06 11:37:43 -07:00
commit 6bd9916310
161 changed files with 4117 additions and 1753 deletions

View file

@ -3,12 +3,11 @@ import { useState } from 'react';
import MobileMenu from './MobileMenu';
import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
export function HamburgerButton() {
const { formatMessage, labels } = useMessages();
const [active, setActive] = useState(false);
const { cloudMode } = useConfig();
const cloudMode = Boolean(process.env.cloudMode);
const menuItems = [
{

View file

@ -25,7 +25,7 @@ export function SettingsTable({
onPageSizeChange,
filterValue,
}) {
const { formatMessage, messages } = useMessages();
const { formatMessage, labels, messages } = useMessages();
const [filter, setFilter] = useState(filterValue);
const { data: value, page, count, pageSize } = data;
@ -42,7 +42,7 @@ export function SettingsTable({
delay={1000}
value={filter}
autoFocus={true}
placeholder="Search"
placeholder={formatMessage(labels.search)}
style={{ maxWidth: '300px', marginBottom: '10px' }}
/>
)}

View file

@ -1,4 +1,5 @@
import { useEffect, useCallback, useState } from 'react';
import { createPortal } from 'react-dom';
import { Button, Row, Column } from 'react-basics';
import { setItem } from 'next-basics';
import useStore, { checkVersion } from 'store/version';
@ -44,7 +45,7 @@ export function UpdateNotice({ user, config }) {
return null;
}
return (
return createPortal(
<Row className={styles.notice}>
<Column variant="two" className={styles.message}>
{formatMessage(messages.newVersionAvailable, { version: `v${latest}` })}
@ -55,7 +56,8 @@ export function UpdateNotice({ user, config }) {
</Button>
<Button onClick={handleDismissClick}>{formatMessage(labels.dismiss)}</Button>
</Column>
</Row>
</Row>,
document.body,
);
}

View file

@ -2,13 +2,14 @@
position: absolute;
max-width: 800px;
gap: 20px;
margin: 20px auto;
justify-self: center;
margin: 80px auto;
align-self: center;
background: var(--base50);
padding: 20px;
border: 1px solid var(--base300);
border-radius: var(--border-radius);
z-index: var(--z-index-popup);
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1);
}
.message {

View file

@ -8,6 +8,7 @@ import { ISO_COUNTRIES, MAP_FILE } from 'lib/constants';
import useTheme from 'components/hooks/useTheme';
import useCountryNames from 'components/hooks/useCountryNames';
import useLocale from 'components/hooks/useLocale';
import useMessages from 'components/hooks/useMessages';
import { formatLongNumber } from 'lib/format';
import { percentFilter } from 'lib/filters';
import styles from './WorldMap.module.css';
@ -17,7 +18,9 @@ export function WorldMap({ data, className }) {
const [tooltip, setTooltipPopup] = useState();
const { theme, colors } = useTheme();
const { locale } = useLocale();
const { formatMessage, labels } = useMessages();
const countryNames = useCountryNames(locale);
const visitorsLabel = formatMessage(labels.visitors).toLocaleLowerCase(locale);
const metrics = useMemo(() => (data ? percentFilter(data) : []), [data]);
function getFillColor(code) {
@ -40,7 +43,7 @@ export function WorldMap({ data, className }) {
function handleHover(code) {
if (code === 'AQ') return;
const country = metrics?.find(({ x }) => x === code);
setTooltipPopup(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} visitors`);
setTooltipPopup(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} ${visitorsLabel}`);
}
return (

View file

@ -7,15 +7,16 @@ let loading = false;
export function useConfig() {
const { config } = useStore();
const { get } = useApi();
const configUrl = process.env.configUrl;
async function loadConfig() {
const data = await get('/config');
const data = await get(configUrl);
loading = false;
setConfig(data);
}
useEffect(() => {
if (!config && !loading) {
if (!config && !loading && configUrl) {
loading = true;
loadConfig();
}

View file

@ -2,18 +2,20 @@ import { produce } from 'immer';
import { useCallback, useEffect, useState } from 'react';
import { useTimezone } from './useTimezone';
import useApi from './useApi';
const baseParameters = {
name: 'Untitled',
description: '',
parameters: {},
};
import useMessages from './useMessages';
export function useReport(reportId, defaultParameters) {
const [report, setReport] = useState(null);
const [isRunning, setIsRunning] = useState(false);
const { get, post } = useApi();
const [timezone] = useTimezone();
const { formatMessage, labels } = useMessages();
const baseParameters = {
name: formatMessage(labels.untitled),
description: '',
parameters: {},
};
const loadReport = async id => {
const data = await get(`/reports/${id}`);

View file

@ -3,16 +3,16 @@ import { useRouter } from 'next/router';
import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages';
import useUser from 'components/hooks/useUser';
import useConfig from 'components/hooks/useConfig';
import styles from './ProfileButton.module.css';
import useLocale from 'components/hooks/useLocale';
import { CURRENT_VERSION } from 'lib/constants';
import styles from './ProfileButton.module.css';
export function ProfileButton() {
const { formatMessage, labels } = useMessages();
const { user } = useUser();
const { cloudMode } = useConfig();
const router = useRouter();
const { dir } = useLocale();
const cloudMode = Boolean(process.env.cloudMode);
const handleSelect = key => {
if (key === 'profile') {
@ -52,6 +52,7 @@ export function ProfileButton() {
<Text>{formatMessage(labels.logout)}</Text>
</Item>
)}
<div className={styles.version}>{`v${CURRENT_VERSION}`}</div>
</Menu>
</Popup>
</PopupTrigger>

View file

@ -8,3 +8,11 @@
gap: 12px;
background: var(--base50);
}
.version {
font-family: monospace;
font-size: 11px;
color: var(--base600);
text-align: right;
margin-right: 10px;
}

View file

@ -9,7 +9,7 @@ export function AppLayout({ title, children }) {
const { user } = useRequireLogin();
const config = useConfig();
if (!user || !config) {
if (!user || !config || config?.uiDisabled) {
return null;
}

View file

@ -1,26 +1,24 @@
import { Icon, Text, Row, Column } from 'react-basics';
import Link from 'next/link';
import { useRouter } from 'next/router';
import classNames from 'classnames';
import Icons from 'components/icons';
import ThemeButton from 'components/input/ThemeButton';
import LanguageButton from 'components/input/LanguageButton';
import ProfileButton from 'components/input/ProfileButton';
import styles from './NavBar.module.css';
import useConfig from 'components/hooks/useConfig';
import useMessages from 'components/hooks/useMessages';
import { useRouter } from 'next/router';
import HamburgerButton from '../common/HamburgerButton';
import HamburgerButton from 'components/common/HamburgerButton';
import styles from './NavBar.module.css';
export function NavBar() {
const { pathname } = useRouter();
const { cloudMode } = useConfig();
const { formatMessage, labels } = useMessages();
const links = [
{ label: formatMessage(labels.dashboard), url: '/dashboard' },
{ label: formatMessage(labels.websites), url: '/websites' },
{ label: formatMessage(labels.reports), url: '/reports' },
!cloudMode && { label: formatMessage(labels.settings), url: '/settings' },
{ label: formatMessage(labels.settings), url: '/settings' },
].filter(n => n);
return (

View file

@ -37,10 +37,6 @@
margin-bottom: 10px;
}
.title {
font-size: 18px;
}
.actions {
flex-basis: 100%;
order: -1;

View file

@ -3,14 +3,13 @@ import { useRouter } from 'next/router';
import SideNav from './SideNav';
import useUser from 'components/hooks/useUser';
import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
import styles from './SettingsLayout.module.css';
export function SettingsLayout({ children }) {
const { user } = useUser();
const { pathname } = useRouter();
const { formatMessage, labels } = useMessages();
const { cloudMode } = useConfig();
const cloudMode = Boolean(process.env.cloudMode);
const items = [
{ key: 'websites', label: formatMessage(labels.websites), url: '/settings/websites' },

View file

@ -13,4 +13,8 @@
.menu {
display: none;
}
.content {
margin-top: 20px;
}
}

View file

@ -129,6 +129,10 @@ export const labels = defineMessages({
reports: { id: 'label.reports', defaultMessage: 'Reports' },
eventData: { id: 'label.event-data', defaultMessage: 'Event data' },
funnel: { id: 'label.funnel', defaultMessage: 'Funnel' },
funnelDescription: {
id: 'label.funnel-description',
defaultMessage: 'Understand the conversion and drop-off rate of users.',
},
url: { id: 'label.url', defaultMessage: 'URL' },
urls: { id: 'label.urls', defaultMessage: 'URLs' },
add: { id: 'label.add', defaultMessage: 'Add' },
@ -167,7 +171,15 @@ export const labels = defineMessages({
overview: { id: 'label.overview', defaultMessage: 'Overview' },
totalRecords: { id: 'label.total-records', defaultMessage: 'Total records' },
insights: { id: 'label.insights', defaultMessage: 'Insights' },
insightsDescription: {
id: 'label.insights-description',
defaultMessage: 'Dive deeper into your data by using segments and filters.',
},
retention: { id: 'label.retention', defaultMessage: 'Retention' },
retentionDescription: {
id: 'label.retention-description',
defaultMessage: 'Measure your website stickiness by tracking how often users return.',
},
dropoff: { id: 'label.dropoff', defaultMessage: 'Dropoff' },
referrer: { id: 'label.referrer', defaultMessage: 'Referrer' },
country: { id: 'label.country', defaultMessage: 'Country' },
@ -179,6 +191,8 @@ export const labels = defineMessages({
day: { id: 'label.day', defaultMessage: 'Day' },
date: { id: 'label.date', defaultMessage: 'Date' },
pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' },
create: { id: 'label.create', defaultMessage: 'Create' },
search: { id: 'label.search', defaultMessage: 'Search' },
});
export const messages = defineMessages({

View file

@ -22,9 +22,7 @@ export function CitiesTable({ websiteId, ...props }) {
<FilterLink id="city" value={city} label={renderLabel(city, country)}>
{country && (
<img
src={`${basePath}/images/flags/${
country?.split?.('-')?.[0]?.toLowerCase() || 'xx'
}.png`}
src={`${basePath}/images/flags/${country?.toLowerCase() || 'xx'}.png`}
alt={country}
/>
)}

View file

@ -54,21 +54,18 @@ export function MetricsTable({
city,
},
],
() =>
get(`/websites/${websiteId}/metrics`, {
() => {
const filters = { url, title, referrer, os, browser, device, country, region, city };
filters[type] = undefined;
return get(`/websites/${websiteId}/metrics`, {
type,
startAt: +startDate,
endAt: +endDate,
url,
title,
referrer,
os,
browser,
device,
country,
region,
city,
}),
...filters,
});
},
{ onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION },
);

View file

@ -13,17 +13,15 @@ export function RegionsTable({ websiteId, ...props }) {
const countryNames = useCountryNames(locale);
const { basePath } = useRouter();
const renderLabel = x => {
return regions[x] ? `${regions[x]}, ${countryNames[x.split('-')[0]]}` : x;
const renderLabel = (code, country) => {
const region = code.includes('-') ? code : `${country}-${code}`;
return regions[region] ? `${regions[region]}, ${countryNames[country]}` : region;
};
const renderLink = ({ x: code }) => {
const renderLink = ({ x: code, country }) => {
return (
<FilterLink id="region" className={locale} value={code} label={renderLabel(code)}>
<img
src={`${basePath}/images/flags/${code?.split('-')?.[0]?.toLowerCase() || 'xx'}.png`}
alt={code}
/>
<FilterLink id="region" className={locale} value={code} label={renderLabel(code, country)}>
<img src={`${basePath}/images/flags/${country?.toLowerCase() || 'xx'}.png`} alt={code} />
</FilterLink>
);
};

View file

@ -1,8 +1,8 @@
import { useState } from 'react';
import { Button, Icon, Icons, Text, Flexbox } from 'react-basics';
import { Button, Icon, Icons, Text } from 'react-basics';
import Link from 'next/link';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import Pager from 'components/common/Pager';
import WebsiteChartList from 'components/pages/websites/WebsiteChartList';
import DashboardSettingsButton from 'components/pages/dashboard/DashboardSettingsButton';
import DashboardEdit from 'components/pages/dashboard/DashboardEdit';
@ -11,23 +11,24 @@ import useApi from 'components/hooks/useApi';
import useDashboard from 'store/dashboard';
import useMessages from 'components/hooks/useMessages';
import useLocale from 'components/hooks/useLocale';
import useApiFilter from 'components/hooks/useApiFilter';
export function Dashboard() {
const { formatMessage, labels, messages } = useMessages();
const dashboard = useDashboard();
const { showCharts, limit, editing } = dashboard;
const [max, setMax] = useState(limit);
const { get, useQuery } = useApi();
const { data, isLoading, error } = useQuery(['websites'], () =>
get('/websites', { includeTeams: 1 }),
);
const hasData = data && data?.data.length !== 0;
const { showCharts, editing } = useDashboard();
const { dir } = useLocale();
function handleMore() {
setMax(max + limit);
}
const { get, useQuery } = useApi();
const { page, handlePageChange } = useApiFilter();
const pageSize = 10;
const {
data: result,
isLoading,
error,
} = useQuery(['websites', page, pageSize], () =>
get('/websites', { includeTeams: 1, page, pageSize }),
);
const { data, count } = result || {};
const hasData = data && data?.length !== 0;
return (
<Page loading={isLoading} error={error}>
@ -48,19 +49,17 @@ export function Dashboard() {
)}
{hasData && (
<>
{editing && <DashboardEdit websites={data?.data} />}
{editing && <DashboardEdit />}
{!editing && (
<WebsiteChartList websites={data?.data} showCharts={showCharts} limit={max} />
)}
{max < data.length && (
<Flexbox justifyContent="center">
<Button onClick={handleMore}>
<Icon rotate={dir === 'rtl' ? 180 : 0}>
<Icons.More />
</Icon>
<Text>{formatMessage(labels.more)}</Text>
</Button>
</Flexbox>
<>
<WebsiteChartList websites={data} showCharts={showCharts} limit={pageSize} />
<Pager
page={page}
pageSize={pageSize}
count={count}
onPageChange={handlePageChange}
/>
</>
)}
</>
)}

View file

@ -5,23 +5,33 @@ import { Button } from 'react-basics';
import { firstBy } from 'thenby';
import useDashboard, { saveDashboard } from 'store/dashboard';
import useMessages from 'components/hooks/useMessages';
import useApi from 'components/hooks/useApi';
import styles from './DashboardEdit.module.css';
import Page from 'components/layout/Page';
const dragId = 'dashboard-website-ordering';
export function DashboardEdit({ websites }) {
export function DashboardEdit() {
const settings = useDashboard();
const { websiteOrder } = settings;
const { formatMessage, labels } = useMessages();
const [order, setOrder] = useState(websiteOrder || []);
const { get, useQuery } = useApi();
const {
data: result,
isLoading,
error,
} = useQuery(['websites'], () => get('/websites', { includeTeams: 1 }));
const { data: websites } = result || {};
const ordered = useMemo(
() =>
websites
const ordered = useMemo(() => {
if (websites) {
return websites
.map(website => ({ ...website, order: order.indexOf(website.id) }))
.sort(firstBy('order')),
[websites, order],
);
.sort(firstBy('order'));
}
return [];
}, [websites, order]);
function handleWebsiteDrag({ destination, source }) {
if (!destination || destination.index === source.index) return;
@ -49,7 +59,7 @@ export function DashboardEdit({ websites }) {
}
return (
<>
<Page loading={isLoading} error={error}>
<div className={styles.buttons}>
<Button onClick={handleSave} variant="action" size="small">
{formatMessage(labels.save)}
@ -95,7 +105,7 @@ export function DashboardEdit({ websites }) {
</Droppable>
</DragDropContext>
</div>
</>
</Page>
);
}

View file

@ -9,7 +9,7 @@ export default function FieldFilterForm({
type,
values,
onSelect,
includeOnlyEquals,
allowFilterSelect = true,
}) {
const { formatMessage, labels } = useMessages();
const [filter, setFilter] = useState('eq');
@ -34,7 +34,7 @@ export default function FieldFilterForm({
<Form>
<FormRow label={label} className={styles.filter}>
<Flexbox gap={10}>
{!includeOnlyEquals && (
{allowFilterSelect && (
<Dropdown
className={styles.dropdown}
items={filters}

View file

@ -18,7 +18,7 @@ function useValues(websiteId, type) {
return { data, error, isLoading };
}
export default function FilterSelectForm({ websiteId, items, onSelect, includeOnlyEquals }) {
export default function FilterSelectForm({ websiteId, items, onSelect, allowFilterSelect }) {
const [field, setField] = useState();
const { data, isLoading } = useValues(websiteId, field?.name);
@ -37,7 +37,7 @@ export default function FilterSelectForm({ websiteId, items, onSelect, includeOn
type={field?.type}
values={data}
onSelect={onSelect}
includeOnlyEquals={includeOnlyEquals}
allowFilterSelect={allowFilterSelect}
/>
);
}

View file

@ -20,6 +20,7 @@ export function ReportHeader({ icon }) {
const { name, description, parameters } = report || {};
const { websiteId, dateRange } = parameters || {};
const defaultName = formatMessage(labels.untitled);
const handleSave = async () => {
if (!report.id) {
@ -39,7 +40,7 @@ export function ReportHeader({ icon }) {
};
const handleNameChange = name => {
updateReport({ name: name || 'Untitled' });
updateReport({ name: name || defaultName });
};
const handleDescriptionChange = description => {
@ -54,7 +55,7 @@ export function ReportHeader({ icon }) {
key={name}
name="name"
value={name}
placeholder={formatMessage(labels.untitled)}
placeholder={defaultName}
onCommit={handleNameChange}
/>
</>

View file

@ -9,6 +9,8 @@ import styles from './ReportTemplates.module.css';
import { useMessages } from 'components/hooks';
function ReportItem({ title, description, url, icon }) {
const { formatMessage, labels } = useMessages();
return (
<div className={styles.report}>
<div className={styles.title}>
@ -22,7 +24,7 @@ function ReportItem({ title, description, url, icon }) {
<Icon>
<Icons.Plus />
</Icon>
<Text>Create</Text>
<Text>{formatMessage(labels.create)}</Text>
</Button>
</Link>
</div>
@ -36,19 +38,19 @@ export function ReportTemplates({ showHeader = true }) {
const reports = [
{
title: formatMessage(labels.insights),
description: 'Dive deeper into your data by using segments and filters.',
description: formatMessage(labels.insightsDescription),
url: '/reports/insights',
icon: <Lightbulb />,
},
{
title: formatMessage(labels.funnel),
description: 'Understand the conversion and drop-off rate of users.',
description: formatMessage(labels.funnelDescription),
url: '/reports/funnel',
icon: <Funnel />,
},
{
title: formatMessage(labels.retention),
description: 'Measure you website stickiness by tracking how often users return.',
description: formatMessage(labels.retentionDescription),
url: '/reports/retention',
icon: <Magnet />,
},

View file

@ -5,6 +5,7 @@ import { useMessages } from 'components/hooks';
import useUser from 'components/hooks/useUser';
import { useState } from 'react';
import { Button, Flexbox, Icon, Icons, Modal, Text } from 'react-basics';
import { REPORT_TYPES } from 'lib/constants';
export function ReportsTable({
data = [],
@ -34,6 +35,15 @@ export function ReportsTable({
{ name: 'action', label: ' ' },
];
const cellRender = (row, data, key) => {
if (key === 'type') {
return formatMessage(
labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === row.type)],
);
}
return data[key];
};
const handleConfirm = () => {
onDelete(report.id);
};
@ -42,6 +52,7 @@ export function ReportsTable({
<>
<SettingsTable
columns={columns}
cellRender={cellRender}
data={data}
showSearch={true}
showPaging={true}

View file

@ -134,7 +134,7 @@ export function EventDataParameters() {
);
})}
<FormButtons>
<SubmitButton variant="primary" disabled={!queryEnabled} loading={isRunning}>
<SubmitButton variant="primary" disabled={!queryEnabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormButtons>

View file

@ -80,7 +80,7 @@ export function FunnelParameters() {
<ParameterList items={urls} onRemove={handleRemoveUrl} />
</FormRow>
<FormButtons>
<SubmitButton variant="primary" disabled={queryDisabled} loading={isRunning}>
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormButtons>

View file

@ -137,7 +137,7 @@ export function InsightsParameters() {
);
})}
<FormButtons>
<SubmitButton variant="primary" disabled={!queryEnabled} loading={isRunning}>
<SubmitButton variant="primary" disabled={!queryEnabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormButtons>

View file

@ -35,7 +35,7 @@ export function RetentionParameters() {
<MonthSelect date={startDate} onChange={handleDateChange} />
</FormRow>
<FormButtons>
<SubmitButton variant="primary" disabled={queryDisabled} loading={isRunning}>
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormButtons>

View file

@ -3,11 +3,13 @@ import classNames from 'classnames';
import { ReportContext } from '../Report';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import { useMessages } from 'components/hooks';
import { useLocale } from 'components/hooks';
import { formatDate } from 'lib/date';
import styles from './RetentionTable.module.css';
export function RetentionTable() {
const { formatMessage, labels } = useMessages();
const { locale } = useLocale();
const { report } = useContext(ReportContext);
const { data } = report || {};
@ -51,7 +53,7 @@ export function RetentionTable() {
{rows.map(({ date, visitors, records }, rowIndex) => {
return (
<div key={rowIndex} className={styles.row}>
<div className={styles.date}>{formatDate(`${date} 00:00:00`, 'PP')}</div>
<div className={styles.date}>{formatDate(`${date} 00:00:00`, 'PP', locale)}</div>
<div className={styles.visitors}>{visitors}</div>
{days.map(day => {
if (totalDays - rowIndex < day) {

View file

@ -6,13 +6,12 @@ import ThemeSetting from 'components/pages/settings/profile/ThemeSetting';
import PasswordChangeButton from './PasswordChangeButton';
import useUser from 'components/hooks/useUser';
import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
import { ROLES } from 'lib/constants';
export function ProfileDetails() {
const { user } = useUser();
const { formatMessage, labels } = useMessages();
const { cloudMode } = useConfig();
const cloudMode = Boolean(process.env.cloudMode);
if (!user) {
return null;

View file

@ -1,6 +1,5 @@
import { useEffect, useState } from 'react';
import { Breadcrumbs, Item, Tabs, useToasts } from 'react-basics';
import Link from 'next/link';
import { Item, Tabs, useToasts } from 'react-basics';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import { ROLES } from 'lib/constants';
@ -44,16 +43,7 @@ export function TeamSettings({ teamId }) {
return (
<Page loading={isLoading || !values}>
<PageHeader
title={
<Breadcrumbs>
<Item>
<Link href="/settings/teams">{formatMessage(labels.teams)}</Link>
</Item>
<Item>{values?.name}</Item>
</Breadcrumbs>
}
/>
<PageHeader title={values?.name} />
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30 }}>
<Item key="details">{formatMessage(labels.details)}</Item>
<Item key="members">{formatMessage(labels.members)}</Item>

View file

@ -1,6 +1,5 @@
import { useEffect, useState } from 'react';
import { Breadcrumbs, Item, Tabs, useToasts } from 'react-basics';
import Link from 'next/link';
import { Item, Tabs, useToasts } from 'react-basics';
import UserEditForm from 'components/pages/settings/users/UserEditForm';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
@ -44,16 +43,7 @@ export function UserSettings({ userId }) {
return (
<Page loading={isLoading || !values}>
<PageHeader
title={
<Breadcrumbs>
<Item>
<Link href="/settings/users">{formatMessage(labels.users)}</Link>
</Item>
<Item>{values?.username}</Item>
</Breadcrumbs>
}
/>
<PageHeader title={values?.username} />
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
<Item key="details">{formatMessage(labels.details)}</Item>
<Item key="websites">{formatMessage(labels.websites)}</Item>

View file

@ -1,15 +1,19 @@
import { TextArea } from 'react-basics';
import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
import { useRouter } from 'next/router';
export function TrackingCode({ websiteId }) {
const { formatMessage, messages } = useMessages();
const { basePath, trackerScriptName } = useConfig();
const { basePath } = useRouter();
const config = useConfig();
const trackerScriptName =
config?.trackerScriptName?.split(',')?.map(n => n.trim())?.[0] || 'script.js';
const url = trackerScriptName?.startsWith('http')
? trackerScriptName
: `${location.origin}${basePath}/${
trackerScriptName?.split(',')?.map(n => n.trim())?.[0] || 'script.js'
}`;
: `${process.env.analyticsUrl || location.origin}${basePath}/${trackerScriptName}`;
const code = `<script async src="${url}" data-website-id="${websiteId}"></script>`;

View file

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Breadcrumbs, Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics';
import { Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics';
import { useRouter } from 'next/router';
import Link from 'next/link';
import Page from 'components/layout/Page';
@ -49,16 +49,7 @@ export function WebsiteSettings({ websiteId, openExternal = false }) {
return (
<Page loading={isLoading || !values}>
<PageHeader
title={
<Breadcrumbs>
<Item>
<Link href="/settings/websites">{formatMessage(labels.websites)}</Link>
</Item>
<Item>{values?.name}</Item>
</Breadcrumbs>
}
>
<PageHeader title={values?.name}>
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
<Button variant="primary">
<Icon>

View file

@ -1,6 +1,5 @@
import classNames from 'classnames';
import { useApi, useDateRange, useMessages, usePageQuery, useSticky } from 'components/hooks';
import RefreshButton from 'components/input/RefreshButton';
import WebsiteDateFilter from 'components/input/WebsiteDateFilter';
import MetricCard from 'components/metrics/MetricCard';
import MetricsBar from 'components/metrics/MetricsBar';
@ -10,7 +9,7 @@ import { formatShortTime } from 'lib/format';
import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row } from 'react-basics';
import styles from './WebsiteMetricsBar.module.css';
export function WebsiteMetricsBar({ websiteId, showFilter = true, showRefresh = true, sticky }) {
export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) {
const { formatMessage, labels } = useMessages();
const { get, useQuery } = useApi();
@ -88,7 +87,7 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, showRefresh =
handleAddFilter(value);
close();
}}
includeOnlyEquals={true}
allowFilterSelect={false}
/>
</PopupForm>
);
@ -161,7 +160,6 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, showRefresh =
<Column defaultSize={12} xl={4}>
<div className={styles.actions}>
{showFilter && <WebsiteFilterButton />}
{showRefresh && <RefreshButton websiteId={websiteId} />}
<WebsiteDateFilter websiteId={websiteId} />
</div>
</Column>

View file

@ -4,7 +4,6 @@ import WebsiteAddForm from 'components/pages/settings/websites/WebsiteAddForm';
import WebsiteList from 'components/pages/settings/websites/WebsitesList';
import { useMessages } from 'components/hooks';
import useUser from 'components/hooks/useUser';
import useConfig from 'components/hooks/useConfig';
import { ROLES } from 'lib/constants';
import { useState } from 'react';
import {
@ -24,8 +23,8 @@ export function WebsitesPage() {
const [tab, setTab] = useState('my-websites');
const [fetch, setFetch] = useState(1);
const { user } = useUser();
const { cloudMode } = useConfig();
const { showToast } = useToasts();
const cloudMode = Boolean(process.env.cloudMode);
const handleSave = async () => {
setFetch(fetch + 1);