mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Reworked settings screens.
This commit is contained in:
parent
c1d301ffdc
commit
0a16ab38e4
58 changed files with 362 additions and 365 deletions
|
|
@ -125,7 +125,7 @@ if (collectApiEndpoint) {
|
|||
const redirects = [
|
||||
{
|
||||
source: '/settings',
|
||||
destination: '/settings/websites',
|
||||
destination: '/settings/profile',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@
|
|||
"@rollup/plugin-node-resolve": "^15.2.0",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@svgr/cli": "^8.1.0",
|
||||
"@svgr/rollup": "^8.1.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
|
|
|
|||
48
pnpm-lock.yaml
generated
48
pnpm-lock.yaml
generated
|
|
@ -210,6 +210,9 @@ importers:
|
|||
'@rollup/plugin-terser':
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4(rollup@3.29.5)
|
||||
'@svgr/cli':
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(typescript@5.8.3)
|
||||
'@svgr/rollup':
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(rollup@3.29.5)(typescript@5.8.3)
|
||||
|
|
@ -2711,6 +2714,11 @@ packages:
|
|||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@svgr/cli@8.1.0':
|
||||
resolution: {integrity: sha512-SnlaLspB610XFXvs3PmhzViHErsXp0yIy4ERyZlHDlO1ro2iYtHMWYk2mztdLD/lBjiA4ZXe4RePON3qU/Tc4A==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
'@svgr/core@8.1.0':
|
||||
resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
|
||||
engines: {node: '>=14'}
|
||||
|
|
@ -2725,6 +2733,12 @@ packages:
|
|||
peerDependencies:
|
||||
'@svgr/core': '*'
|
||||
|
||||
'@svgr/plugin-prettier@8.1.0':
|
||||
resolution: {integrity: sha512-o4/uFI8G64tAjBZ4E7gJfH+VP7Qi3T0+M4WnIsP91iFnGPqs5WvPDkpZALXPiyWEtzfYs1Rmwy1Zdfu8qoZuKw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@svgr/core': '*'
|
||||
|
||||
'@svgr/plugin-svgo@8.1.0':
|
||||
resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==}
|
||||
engines: {node: '>=14'}
|
||||
|
|
@ -3581,6 +3595,10 @@ packages:
|
|||
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
commander@9.5.0:
|
||||
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
|
||||
engines: {node: ^12.20.0 || >=14}
|
||||
|
||||
common-tags@1.8.2:
|
||||
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
|
@ -3788,6 +3806,10 @@ packages:
|
|||
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
dashify@2.0.0:
|
||||
resolution: {integrity: sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
|
@ -10388,6 +10410,22 @@ snapshots:
|
|||
'@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.10)
|
||||
'@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.10)
|
||||
|
||||
'@svgr/cli@8.1.0(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@svgr/core': 8.1.0(typescript@5.8.3)
|
||||
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))
|
||||
'@svgr/plugin-prettier': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))
|
||||
'@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))(typescript@5.8.3)
|
||||
camelcase: 6.3.0
|
||||
chalk: 4.1.2
|
||||
commander: 9.5.0
|
||||
dashify: 2.0.0
|
||||
glob: 8.1.0
|
||||
snake-case: 3.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@svgr/core@8.1.0(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10
|
||||
|
|
@ -10414,6 +10452,12 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@svgr/plugin-prettier@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@svgr/core': 8.1.0(typescript@5.8.3)
|
||||
deepmerge: 4.3.1
|
||||
prettier: 2.8.8
|
||||
|
||||
'@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@svgr/core': 8.1.0(typescript@5.8.3)
|
||||
|
|
@ -11400,6 +11444,8 @@ snapshots:
|
|||
|
||||
commander@8.3.0: {}
|
||||
|
||||
commander@9.5.0: {}
|
||||
|
||||
common-tags@1.8.2: {}
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
|
@ -11690,6 +11736,8 @@ snapshots:
|
|||
dependencies:
|
||||
assert-plus: 1.0.0
|
||||
|
||||
dashify@2.0.0: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { Column, Heading } from '@umami/react-zen';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
|
||||
export function BoardsPage() {
|
||||
return (
|
||||
<Column>
|
||||
<Heading>My Boards</Heading>
|
||||
<PageHeader title="My Boards" />
|
||||
<Link href="/teams/3a97e34a-7f9d-4de2-8754-ed81714b528d/boards/86d4095c-a2a8-4fc8-9521-103e858e2b41">
|
||||
Board 1
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Link from 'next/link';
|
|||
import Script from 'next/script';
|
||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||
import { Page } from '@/components/common/Page';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { EventsChart } from '@/components/metrics/EventsChart';
|
||||
import { WebsiteChart } from '../websites/[websiteId]/WebsiteChart';
|
||||
import { useApi, useNavigation } from '@/components/hooks';
|
||||
|
|
@ -118,9 +118,9 @@ export function TestConsole({ websiteId }: { websiteId: string }) {
|
|||
|
||||
return (
|
||||
<Page isLoading={isLoading} error={error}>
|
||||
<PageHeader title="Test console">
|
||||
<SectionHeader title="Test console">
|
||||
<WebsiteSelect websiteId={website?.id} onSelect={handleChange} />
|
||||
</PageHeader>
|
||||
</SectionHeader>
|
||||
{website && (
|
||||
<div className={styles.container}>
|
||||
<Script
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
import { Icon, Icons, Loading, Text } from '@umami/react-zen';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { Pager } from '@/components/common/Pager';
|
||||
import { WebsiteChartList } from '../websites/[websiteId]/WebsiteChartList';
|
||||
import { DashboardSettingsButton } from '@/app/(main)/dashboard/DashboardSettingsButton';
|
||||
|
|
@ -30,9 +30,9 @@ export function DashboardPage() {
|
|||
|
||||
return (
|
||||
<section style={{ marginBottom: 60 }}>
|
||||
<PageHeader title={formatMessage(labels.dashboard)}>
|
||||
<SectionHeader title={formatMessage(labels.dashboard)}>
|
||||
{!editing && hasData && <DashboardSettingsButton />}
|
||||
</PageHeader>
|
||||
</SectionHeader>
|
||||
{!hasData && (
|
||||
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)}>
|
||||
<LinkButton href={renderTeamUrl('/settings')}>
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.field {
|
||||
width: 200px;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function ProfileHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return <PageHeader title={formatMessage(labels.profile)}></PageHeader>;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
.container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.container {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
'use client';
|
||||
import { ProfileHeader } from './ProfileHeader';
|
||||
import { ProfileSettings } from './ProfileSettings';
|
||||
import styles from './ProfilePage.module.css';
|
||||
|
||||
export function ProfilePage() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ProfileHeader />
|
||||
<ProfileSettings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
.buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.active {
|
||||
border: 2px solid var(--primary-color);
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import classNames from 'classnames';
|
||||
import { Button, Icon, useTheme } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import styles from './ThemeSetting.module.css';
|
||||
|
||||
export function ThemeSetting() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<div className={styles.buttons}>
|
||||
<Button
|
||||
className={classNames({ [styles.active]: theme === 'light' })}
|
||||
onPress={() => setTheme('light')}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Sun />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button
|
||||
className={classNames({ [styles.active]: theme === 'dark' })}
|
||||
onPress={() => setTheme('dark')}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Moon />
|
||||
</Icon>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
.dropdown {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
div.menu {
|
||||
max-height: 300px;
|
||||
width: 300px;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { Icon, Icons, Text } from '@umami/react-zen';
|
||||
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
|
@ -11,7 +11,7 @@ export function ReportsHeader() {
|
|||
const canEdit = user.role !== ROLES.viewOnly;
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.reports)}>
|
||||
<SectionHeader title={formatMessage(labels.reports)}>
|
||||
{canEdit && (
|
||||
<LinkButton href={renderTeamUrl('/reports/create')} variant="primary">
|
||||
<Icon>
|
||||
|
|
@ -20,6 +20,6 @@ export function ReportsHeader() {
|
|||
<Text>{formatMessage(labels.createReport)}</Text>
|
||||
</LinkButton>
|
||||
)}
|
||||
</PageHeader>
|
||||
</SectionHeader>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Icon, Text, Row, Column, Grid } from '@umami/react-zen';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
||||
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
|
||||
|
|
@ -61,7 +61,7 @@ export function ReportTemplates({ showHeader = true }: { showHeader?: boolean })
|
|||
|
||||
return (
|
||||
<>
|
||||
{showHeader && <PageHeader title={formatMessage(labels.reports)} />}
|
||||
{showHeader && <SectionHeader title={formatMessage(labels.reports)} />}
|
||||
<Grid columns="repeat(3, minmax(200px, 1fr))" gap="3">
|
||||
{reports.map(({ title, description, url, icon }) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,31 +1,49 @@
|
|||
'use client';
|
||||
import { ReactNode } from 'react';
|
||||
import { Grid, Column } from '@umami/react-zen';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { SideBar } from '@/components/common/SideBar';
|
||||
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { SideMenu } from '@/components/common/SideMenu';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||
const { user } = useLoginQuery();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'websites',
|
||||
id: 'profile',
|
||||
label: formatMessage(labels.profile),
|
||||
url: '/settings/profile',
|
||||
},
|
||||
{ id: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
|
||||
user.isAdmin && {
|
||||
id: 'websites',
|
||||
label: formatMessage(labels.websites),
|
||||
url: '/settings/websites',
|
||||
},
|
||||
{ key: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
|
||||
user.isAdmin && {
|
||||
key: 'users',
|
||||
id: 'users',
|
||||
label: formatMessage(labels.users),
|
||||
url: '/settings/users',
|
||||
},
|
||||
].filter(n => n);
|
||||
|
||||
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<SideBar items={items} />
|
||||
<Column>{children}</Column>
|
||||
</Grid>
|
||||
<Column gap="6">
|
||||
<PageHeader title={formatMessage(labels.settings)} />
|
||||
|
||||
<Grid columns="160px 1fr" gap="6">
|
||||
<Column marginTop="6">
|
||||
<SideMenu items={items} selectedKey={value} />
|
||||
</Column>
|
||||
<Column>
|
||||
<Panel>{children}</Panel>
|
||||
</Column>
|
||||
</Grid>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { DateFilter } from '@/components/input/DateFilter';
|
||||
import { Button, Flexbox } from '@umami/react-zen';
|
||||
import { Button, Row } from '@umami/react-zen';
|
||||
import { useDateRange, useMessages } from '@/components/hooks';
|
||||
import { DEFAULT_DATE_RANGE } from '@/lib/constants';
|
||||
import { DateRange } from '@/lib/types';
|
||||
import styles from './DateRangeSetting.module.css';
|
||||
|
||||
export function DateRangeSetting() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
@ -14,15 +13,14 @@ export function DateRangeSetting() {
|
|||
const handleReset = () => saveDateRange(DEFAULT_DATE_RANGE);
|
||||
|
||||
return (
|
||||
<Flexbox gap="3" width="300px">
|
||||
<Row gap="3">
|
||||
<DateFilter
|
||||
className={styles.field}
|
||||
value={value}
|
||||
startDate={dateRange.startDate}
|
||||
endDate={dateRange.endDate}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||
</Flexbox>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button, Icon, Text, useToast, DialogTrigger, Dialog, Modal } from '@umami/react-zen';
|
||||
import { PasswordEditForm } from '@/app/(main)/profile/PasswordEditForm';
|
||||
import { PasswordEditForm } from './PasswordEditForm';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ export function PasswordChangeButton() {
|
|||
return (
|
||||
<DialogTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.Lock />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.changePassword)}</Text>
|
||||
8
src/app/(main)/settings/profile/ProfileHeader.tsx
Normal file
8
src/app/(main)/settings/profile/ProfileHeader.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function ProfileHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return <SectionHeader title={formatMessage(labels.profile)}></SectionHeader>;
|
||||
}
|
||||
16
src/app/(main)/settings/profile/ProfilePage.tsx
Normal file
16
src/app/(main)/settings/profile/ProfilePage.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
'use client';
|
||||
import { ProfileSettings } from './ProfileSettings';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
|
||||
export function ProfilePage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title={formatMessage(labels.profile)} />
|
||||
|
||||
<ProfileSettings />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { Column, Label } from '@umami/react-zen';
|
||||
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
|
||||
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
|
||||
import { LanguageSetting } from '@/app/(main)/profile/LanguageSetting';
|
||||
import { ThemeSetting } from '@/app/(main)/profile/ThemeSetting';
|
||||
import { PasswordChangeButton } from './PasswordChangeButton';
|
||||
import { Row, Column, Label } from '@umami/react-zen';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { TimezoneSetting } from './TimezoneSetting';
|
||||
import { DateRangeSetting } from './DateRangeSetting';
|
||||
import { LanguageSetting } from './LanguageSetting';
|
||||
import { ThemeSetting } from './ThemeSetting';
|
||||
import { PasswordChangeButton } from './PasswordChangeButton';
|
||||
|
||||
export function ProfileSettings() {
|
||||
const { user } = useLoginQuery();
|
||||
|
|
@ -47,7 +47,9 @@ export function ProfileSettings() {
|
|||
{!cloudMode && (
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.password)}</Label>
|
||||
<PasswordChangeButton />
|
||||
<Row>
|
||||
<PasswordChangeButton />
|
||||
</Row>
|
||||
</Column>
|
||||
)}
|
||||
|
||||
24
src/app/(main)/settings/profile/ThemeSetting.tsx
Normal file
24
src/app/(main)/settings/profile/ThemeSetting.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { Row, Button, Icon, useTheme } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
|
||||
export function ThemeSetting() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<Row gap>
|
||||
<Button
|
||||
variant={theme === 'light' ? 'primary' : 'secondary'}
|
||||
onPress={() => setTheme('light')}
|
||||
>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.Sun />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button variant={theme === 'dark' ? 'primary' : 'secondary'} onPress={() => setTheme('dark')}>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.Moon />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ import { useState } from 'react';
|
|||
import { Row, Select, ListItem, Button } from '@umami/react-zen';
|
||||
import { useTimezone, useMessages } from '@/components/hooks';
|
||||
import { getTimezone } from '@/lib/date';
|
||||
import styles from './TimezoneSetting.module.css';
|
||||
|
||||
const timezones = Intl.supportedValuesOf('timeZone');
|
||||
|
||||
|
|
@ -25,7 +24,6 @@ export function TimezoneSetting() {
|
|||
return (
|
||||
<Row gap="3">
|
||||
<Select
|
||||
className={styles.dropdown}
|
||||
selectedKey={timezone}
|
||||
onChange={(value: any) => saveTimezone(value)}
|
||||
allowSearch={true}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Row } from '@umami/react-zen';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { TeamsJoinButton } from './TeamsJoinButton';
|
||||
|
|
@ -11,11 +11,11 @@ export function TeamsHeader({ allowCreate = true }: { allowCreate?: boolean }) {
|
|||
const cloudMode = !!process.env.cloudMode;
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.teams)}>
|
||||
<SectionHeader title={formatMessage(labels.teams)}>
|
||||
<Row gap="3">
|
||||
{!cloudMode && <TeamsJoinButton />}
|
||||
{allowCreate && user.role !== ROLES.viewOnly && <TeamsAddButton />}
|
||||
</Row>
|
||||
</PageHeader>
|
||||
</SectionHeader>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export function TeamsJoinButton() {
|
|||
return (
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">
|
||||
<Icon>
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.AddUser />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.joinTeam)}</Text>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
'use client';
|
||||
import { TeamsDataTable } from './TeamsDataTable';
|
||||
import { TeamsHeader } from './TeamsHeader';
|
||||
import { Column } from '@umami/react-zen';
|
||||
|
||||
export function TeamsSettingsPage() {
|
||||
return (
|
||||
<>
|
||||
<Column gap>
|
||||
<TeamsHeader />
|
||||
<TeamsDataTable />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { UserAddButton } from './UserAddButton';
|
||||
|
||||
export function UsersHeader({ onAdd }: { onAdd?: () => void }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.users)}>
|
||||
<UserAddButton onSave={onAdd} />
|
||||
</PageHeader>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,21 @@
|
|||
'use client';
|
||||
import { UsersDataTable } from './UsersDataTable';
|
||||
import { UsersHeader } from './UsersHeader';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { UserAddButton } from '@/app/(main)/settings/users/UserAddButton';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function UsersSettingsPage() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleSave = () => {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UsersHeader />
|
||||
<Column gap>
|
||||
<SectionHeader title={formatMessage(labels.users)}>
|
||||
<UserAddButton onSave={handleSave} />
|
||||
</SectionHeader>
|
||||
<UsersDataTable />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useContext } from 'react';
|
|||
import { Tabs, Tab, TabList, TabPanel } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { UserEditForm } from './UserEditForm';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { UserWebsites } from './UserWebsites';
|
||||
import { UserContext } from './UserProvider';
|
||||
|
|
@ -13,7 +13,7 @@ export function UserSettings({ userId }: { userId: string }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={user?.username} icon={<Icons.User />} />
|
||||
<SectionHeader title={user?.username} icon={<Icons.User />} />
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { WebsiteAddButton } from './WebsiteAddButton';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
|
||||
export interface WebsitesHeaderProps {
|
||||
allowCreate?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
'use client';
|
||||
import { useLoginQuery } from '@/components/hooks';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { WebsitesDataTable } from './WebsitesDataTable';
|
||||
import { WebsitesHeader } from './WebsitesHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { Column } from '@umami/react-zen';
|
||||
|
||||
export function WebsitesSettingsPage({ teamId }: { teamId: string }) {
|
||||
const { user } = useLoginQuery();
|
||||
const canCreate = user.role !== ROLES.viewOnly;
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebsitesHeader allowCreate={canCreate} />
|
||||
<Column gap>
|
||||
<SectionHeader title={formatMessage(labels.websites)}>
|
||||
{canCreate && <WebsiteAddButton teamId={teamId} />}
|
||||
</SectionHeader>
|
||||
<WebsitesDataTable teamId={teamId} />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { useContext } from 'react';
|
||||
import { Button, Icon, Tabs, TabList, Tab, TabPanel, Text } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { Icon, Tabs, TabList, Tab, TabPanel, Text } from '@umami/react-zen';
|
||||
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { ShareUrl } from './ShareUrl';
|
||||
import { TrackingCode } from './TrackingCode';
|
||||
import { WebsiteData } from './WebsiteData';
|
||||
import { WebsiteEditForm } from './WebsiteEditForm';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
||||
export function WebsiteSettings({
|
||||
websiteId,
|
||||
|
|
@ -22,16 +22,18 @@ export function WebsiteSettings({
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={website?.name} icon={<Icons.Globe />}>
|
||||
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
<SectionHeader title={website?.name} icon={<Icons.Globe />}>
|
||||
<LinkButton
|
||||
variant="primary"
|
||||
href={`/websites/${websiteId}`}
|
||||
target={openExternal ? '_blank' : null}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</LinkButton>
|
||||
</SectionHeader>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
|
|
|
|||
|
|
@ -2,34 +2,48 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Grid, Column } from '@umami/react-zen';
|
||||
import { SideBar } from '@/components/common/SideBar';
|
||||
import { SideMenu } from '@/components/common/SideMenu';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
|
||||
export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { teamId } = useNavigation();
|
||||
const { pathname, teamId } = useNavigation();
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'team',
|
||||
id: 'team',
|
||||
label: formatMessage(labels.team),
|
||||
url: `/teams/${teamId}/settings/team`,
|
||||
},
|
||||
{
|
||||
key: 'websites',
|
||||
id: 'websites',
|
||||
label: formatMessage(labels.websites),
|
||||
url: `/teams/${teamId}/settings/websites`,
|
||||
},
|
||||
{
|
||||
key: 'members',
|
||||
id: 'members',
|
||||
label: formatMessage(labels.members),
|
||||
url: `/teams/${teamId}/settings/members`,
|
||||
},
|
||||
].filter(n => n);
|
||||
|
||||
const value = items.find(({ url }) => pathname.endsWith(url))?.id;
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<SideBar items={items} />
|
||||
<Column>{children}</Column>
|
||||
</Grid>
|
||||
<Column gap="6">
|
||||
<PageHeader title={formatMessage(labels.teamSettings)} />
|
||||
|
||||
<Column gap="6">
|
||||
<Grid columns="200px 1fr">
|
||||
<Column marginTop="6">
|
||||
<SideMenu items={items} selectedKey={value} />
|
||||
</Column>
|
||||
<Column>
|
||||
<Panel>{children}</Panel>
|
||||
</Column>
|
||||
</Grid>
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
'use client';
|
||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||
import { TeamMembersDataTable } from './TeamMembersDataTable';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useContext } from 'react';
|
||||
import { Column } from '@umami/react-zen';
|
||||
|
||||
export function TeamMembersPage({ teamId }: { teamId: string }) {
|
||||
const team = useContext(TeamContext);
|
||||
|
|
@ -18,9 +19,9 @@ export function TeamMembersPage({ teamId }: { teamId: string }) {
|
|||
) && user.role !== ROLES.viewOnly;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={formatMessage(labels.members)} />
|
||||
<Column gap>
|
||||
<SectionHeader title={formatMessage(labels.members)} />
|
||||
<TeamMembersDataTable teamId={teamId} allowEdit={canEdit} />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
|
||||
import { TeamManage } from './TeamManage';
|
||||
import { TeamEditForm } from './TeamEditForm';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function TeamDetails({ teamId }: { teamId: string }) {
|
||||
const team = useContext(TeamContext);
|
||||
|
|
@ -27,24 +25,22 @@ export function TeamDetails({ teamId }: { teamId: string }) {
|
|||
) && user.role !== ROLES.viewOnly;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<PageHeader title={team?.name} icon={<Icons.Users />}>
|
||||
<Column gap>
|
||||
<SectionHeader title={team?.name}>
|
||||
{!isTeamOwner && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
{isTeamOwner && <Tab id="manage">{formatMessage(labels.manage)}</Tab>}
|
||||
</TabList>
|
||||
<TabPanel id="details">
|
||||
<TeamEditForm teamId={teamId} allowEdit={canEdit} />
|
||||
</TabPanel>
|
||||
<TabPanel id="manage">
|
||||
<TeamManage teamId={teamId} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Panel>
|
||||
</SectionHeader>
|
||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
{isTeamOwner && <Tab id="manage">{formatMessage(labels.manage)}</Tab>}
|
||||
</TabList>
|
||||
<TabPanel id="details">
|
||||
<TeamEditForm teamId={teamId} allowEdit={canEdit} />
|
||||
</TabPanel>
|
||||
<TabPanel id="manage">
|
||||
<TeamManage teamId={teamId} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { TeamWebsitesDataTable } from './TeamWebsitesDataTable';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useContext } from 'react';
|
||||
import { Column } from '@umami/react-zen';
|
||||
|
||||
export function TeamWebsitesPage({ teamId }: { teamId: string }) {
|
||||
const team = useContext(TeamContext);
|
||||
|
|
@ -18,11 +19,11 @@ export function TeamWebsitesPage({ teamId }: { teamId: string }) {
|
|||
) && user.role !== ROLES.viewOnly;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={formatMessage(labels.websites)}>
|
||||
<Column gap>
|
||||
<SectionHeader title={formatMessage(labels.websites)}>
|
||||
{canEdit && <WebsiteAddButton teamId={teamId} />}
|
||||
</PageHeader>
|
||||
</SectionHeader>
|
||||
<TeamWebsitesDataTable teamId={teamId} allowEdit={canEdit} />
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
'use client';
|
||||
import { WebsitesHeader } from '@/app/(main)/settings/websites/WebsitesHeader';
|
||||
import { WebsitesDataTable } from '@/app/(main)/settings/websites/WebsitesDataTable';
|
||||
import { useNavigation } from '@/components/hooks';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
export function WebsitesPage() {
|
||||
const { teamId } = useNavigation();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebsitesHeader />
|
||||
<WebsitesDataTable teamId={teamId} allowEdit={false} />
|
||||
</>
|
||||
<Column gap="6">
|
||||
<PageHeader title={formatMessage(labels.websites)}>
|
||||
<WebsiteAddButton teamId={teamId} />
|
||||
</PageHeader>
|
||||
<Panel>
|
||||
<WebsitesDataTable teamId={teamId} allowEdit={false} />
|
||||
</Panel>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Grid, Heading, Column, Row } from '@umami/react-zen';
|
||||
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { SideBar } from '@/components/common/SideBar';
|
||||
import { SideMenu } from '@/components/common/SideMenu';
|
||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||
import { ChangeLabel } from '@/components/metrics/ChangeLabel';
|
||||
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
||||
|
|
@ -146,7 +146,7 @@ export function WebsiteCompareTables({ websiteId }: { websiteId: string }) {
|
|||
return (
|
||||
<Panel>
|
||||
<Grid columns={{ xs: '1fr', lg: '200px 1fr 1fr' }} gap="6">
|
||||
<SideBar items={items} selectedKey={view} />
|
||||
<SideMenu items={items} selectedKey={view} />
|
||||
<Column border="left" paddingLeft="6">
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
<Heading size="1">{formatMessage(labels.previous)}</Heading>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Icon, Icons, Text, Grid, Column } from '@umami/react-zen';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { SideBar } from '@/components/common/SideBar';
|
||||
import { SideMenu } from '@/components/common/SideMenu';
|
||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
||||
import { CountriesTable } from '@/components/metrics/CountriesTable';
|
||||
|
|
@ -17,7 +17,6 @@ import { RegionsTable } from '@/components/metrics/RegionsTable';
|
|||
import { ScreenTable } from '@/components/metrics/ScreenTable';
|
||||
import { TagsTable } from '@/components/metrics/TagsTable';
|
||||
import { ChannelsTable } from '@/components/metrics/ChannelsTable';
|
||||
import { Panel } from '@/components/common/Panel';
|
||||
|
||||
const views = {
|
||||
url: PagesTable,
|
||||
|
|
@ -56,77 +55,77 @@ export function WebsiteExpandedView({
|
|||
|
||||
const items = [
|
||||
{
|
||||
key: 'url',
|
||||
id: 'url',
|
||||
label: formatMessage(labels.pages),
|
||||
url: renderUrl({ view: 'url' }),
|
||||
},
|
||||
{
|
||||
key: 'referrer',
|
||||
id: 'referrer',
|
||||
label: formatMessage(labels.referrers),
|
||||
url: renderUrl({ view: 'referrer' }),
|
||||
},
|
||||
{
|
||||
key: 'channel',
|
||||
id: 'channel',
|
||||
label: formatMessage(labels.channels),
|
||||
url: renderUrl({ view: 'channel' }),
|
||||
},
|
||||
{
|
||||
key: 'browser',
|
||||
id: 'browser',
|
||||
label: formatMessage(labels.browsers),
|
||||
url: renderUrl({ view: 'browser' }),
|
||||
},
|
||||
{
|
||||
key: 'os',
|
||||
id: 'os',
|
||||
label: formatMessage(labels.os),
|
||||
url: renderUrl({ view: 'os' }),
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
id: 'device',
|
||||
label: formatMessage(labels.devices),
|
||||
url: renderUrl({ view: 'device' }),
|
||||
},
|
||||
{
|
||||
key: 'country',
|
||||
id: 'country',
|
||||
label: formatMessage(labels.countries),
|
||||
url: renderUrl({ view: 'country' }),
|
||||
},
|
||||
{
|
||||
key: 'region',
|
||||
id: 'region',
|
||||
label: formatMessage(labels.regions),
|
||||
url: renderUrl({ view: 'region' }),
|
||||
},
|
||||
{
|
||||
key: 'city',
|
||||
id: 'city',
|
||||
label: formatMessage(labels.cities),
|
||||
url: renderUrl({ view: 'city' }),
|
||||
},
|
||||
{
|
||||
key: 'language',
|
||||
id: 'language',
|
||||
label: formatMessage(labels.languages),
|
||||
url: renderUrl({ view: 'language' }),
|
||||
},
|
||||
{
|
||||
key: 'screen',
|
||||
id: 'screen',
|
||||
label: formatMessage(labels.screens),
|
||||
url: renderUrl({ view: 'screen' }),
|
||||
},
|
||||
{
|
||||
key: 'event',
|
||||
id: 'event',
|
||||
label: formatMessage(labels.events),
|
||||
url: renderUrl({ view: 'event' }),
|
||||
},
|
||||
{
|
||||
key: 'query',
|
||||
id: 'query',
|
||||
label: formatMessage(labels.queryParameters),
|
||||
url: renderUrl({ view: 'query' }),
|
||||
},
|
||||
{
|
||||
key: 'host',
|
||||
id: 'host',
|
||||
label: formatMessage(labels.hosts),
|
||||
url: renderUrl({ view: 'host' }),
|
||||
},
|
||||
{
|
||||
key: 'tag',
|
||||
id: 'tag',
|
||||
label: formatMessage(labels.tags),
|
||||
url: renderUrl({ view: 'tag' }),
|
||||
},
|
||||
|
|
@ -143,20 +142,18 @@ export function WebsiteExpandedView({
|
|||
</Icon>
|
||||
<Text>{formatMessage(labels.back)}</Text>
|
||||
</LinkButton>
|
||||
<SideBar items={items} selectedKey={view} />
|
||||
<SideMenu items={items} selectedKey={view} />
|
||||
</Column>
|
||||
<Column>
|
||||
<Panel>
|
||||
<DetailsComponent
|
||||
websiteId={websiteId}
|
||||
domainName={domainName}
|
||||
animate={false}
|
||||
virtualize={true}
|
||||
itemCount={25}
|
||||
allowFilter={true}
|
||||
allowSearch={true}
|
||||
/>
|
||||
</Panel>
|
||||
<DetailsComponent
|
||||
websiteId={websiteId}
|
||||
domainName={domainName}
|
||||
animate={false}
|
||||
virtualize={true}
|
||||
itemCount={25}
|
||||
allowFilter={true}
|
||||
allowSearch={true}
|
||||
/>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Page } from '@/components/common/Page';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { useApi, useMessages } from '@/components/hooks';
|
||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ export function RealtimeHome() {
|
|||
|
||||
return (
|
||||
<Page isLoading={isLoading || data?.length > 0} error={error}>
|
||||
<PageHeader title={formatMessage(labels.realtime)} />
|
||||
<SectionHeader title={formatMessage(labels.realtime)} />
|
||||
{data?.length === 0 && (
|
||||
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -36,19 +36,19 @@ export function RealtimeLog({ data }: { data: RealtimeData }) {
|
|||
const buttons = [
|
||||
{
|
||||
label: formatMessage(labels.all),
|
||||
key: TYPE_ALL,
|
||||
id: TYPE_ALL,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.views),
|
||||
key: TYPE_PAGEVIEW,
|
||||
id: TYPE_PAGEVIEW,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.visitors),
|
||||
key: TYPE_SESSION,
|
||||
id: TYPE_SESSION,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.events),
|
||||
key: TYPE_EVENT,
|
||||
id: TYPE_EVENT,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ export function RealtimeLog({ data }: { data: RealtimeData }) {
|
|||
<div className={styles.table}>
|
||||
<div className={styles.actions}>
|
||||
<SearchField className={styles.search} value={search} onSearch={setSearch} />
|
||||
<FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />
|
||||
<FilterButtons items={buttons} value={filter} onChange={setFilter} />
|
||||
</div>
|
||||
<div className={styles.header}>{formatMessage(labels.activity)}</div>
|
||||
<div className={styles.body}>
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ export function DataGrid({
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column gap="4">
|
||||
{allowSearch && (hasData || search) && (
|
||||
<Row width="280px" alignItems="center" marginBottom="6">
|
||||
<Row width="280px" alignItems="center">
|
||||
<SearchField
|
||||
value={search}
|
||||
onSearch={handleSearch}
|
||||
|
|
@ -71,6 +71,6 @@ export function DataGrid({
|
|||
</Row>
|
||||
)}
|
||||
</LoadingPanel>
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useLocale } from '@/components/hooks';
|
|||
|
||||
export interface LinkButtonProps {
|
||||
href: string;
|
||||
target?: string;
|
||||
scroll?: boolean;
|
||||
variant?: any;
|
||||
children?: ReactNode;
|
||||
|
|
@ -12,8 +13,9 @@ export interface LinkButtonProps {
|
|||
|
||||
export function LinkButton({
|
||||
href,
|
||||
variant = 'quiet',
|
||||
variant,
|
||||
scroll = true,
|
||||
target,
|
||||
children,
|
||||
...props
|
||||
}: LinkButtonProps) {
|
||||
|
|
@ -21,7 +23,7 @@ export function LinkButton({
|
|||
|
||||
return (
|
||||
<Button {...props} variant={variant} asChild>
|
||||
<Link href={href} dir={dir} scroll={scroll}>
|
||||
<Link href={href} dir={dir} scroll={scroll} target={target}>
|
||||
{children}
|
||||
</Link>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
.page {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
align-self: stretch;
|
||||
flex-wrap: wrap;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.header a {
|
||||
color: var(--base600);
|
||||
}
|
||||
|
||||
.header a:hover {
|
||||
color: var(--base900);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
gap: 20px;
|
||||
height: 60px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--base700);
|
||||
margin-inline-end: 1rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,10 +15,10 @@ export function PageHeader({
|
|||
children?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Row justifyContent="space-between" alignItems="center" marginY="6">
|
||||
<Row justifyContent="space-between" alignItems="center" paddingY="6" border="bottom">
|
||||
<Row gap="3">
|
||||
{icon && <Icon size="lg">{icon}</Icon>}
|
||||
{title && <Heading size="2">{title}</Heading>}
|
||||
{icon && <Icon>{icon}</Icon>}
|
||||
{title && <Heading size="4">{title}</Heading>}
|
||||
{description && <Text color="muted">{description}</Text>}
|
||||
</Row>
|
||||
<Row justifyContent="flex-end">{children}</Row>
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
.pager {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: var(--font-size-md);
|
||||
margin: 0 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: var(--base600);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.pager {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.nav {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
27
src/components/common/SectionHeader.tsx
Normal file
27
src/components/common/SectionHeader.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Heading, Icon, Row, Text } from '@umami/react-zen';
|
||||
|
||||
export function SectionHeader({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: ReactNode;
|
||||
allowEdit?: boolean;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Row justifyContent="space-between" alignItems="center" height="60px">
|
||||
<Row gap="3" alignItems="center">
|
||||
{icon && <Icon>{icon}</Icon>}
|
||||
{title && <Heading size="3">{title}</Heading>}
|
||||
{description && <Text color="muted">{description}</Text>}
|
||||
</Row>
|
||||
<Row justifyContent="flex-end">{children}</Row>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { Text, List, ListItem } from '@umami/react-zen';
|
||||
|
||||
export interface MenuNavProps {
|
||||
items: any[];
|
||||
selectedKey?: string;
|
||||
}
|
||||
|
||||
export function SideBar({ items, selectedKey }: MenuNavProps) {
|
||||
return (
|
||||
<List>
|
||||
{items.map(({ key, label, url }) => {
|
||||
return (
|
||||
<ListItem key={key} href={url}>
|
||||
<Text weight={key === selectedKey ? 'bold' : 'regular'}>{label}</Text>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
20
src/components/common/SideMenu.tsx
Normal file
20
src/components/common/SideMenu.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Text, List, ListItem } from '@umami/react-zen';
|
||||
|
||||
export interface MenuNavProps {
|
||||
items: { id: string; label: string; url: string }[];
|
||||
selectedKey?: string;
|
||||
}
|
||||
|
||||
export function SideMenu({ items, selectedKey }: MenuNavProps) {
|
||||
return (
|
||||
<List>
|
||||
{items.map(({ id, label, url }) => {
|
||||
return (
|
||||
<ListItem key={id} id={id} href={url}>
|
||||
<Text weight={id === selectedKey ? 'bold' : 'regular'}>{label}</Text>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
|
@ -106,7 +106,6 @@ export function DateFilter({
|
|||
placeholder={formatMessage(labels.selectDate)}
|
||||
onSelectionChange={handleChange}
|
||||
renderValue={renderValue}
|
||||
style={{ width: 'auto' }}
|
||||
>
|
||||
{options.map(({ label, value, divider }: any) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export function ProfileButton() {
|
|||
|
||||
const handleSelect = (key: Key) => {
|
||||
if (key === 'profile') {
|
||||
router.push('/profile');
|
||||
router.push('/settings/profile');
|
||||
}
|
||||
if (key === 'logout') {
|
||||
router.push('/logout');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button, Icon, DialogTrigger, Popover, Column, Label } from '@umami/react-zen';
|
||||
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
|
||||
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
|
||||
import { TimezoneSetting } from '@/app/(main)/settings/profile/TimezoneSetting';
|
||||
import { DateRangeSetting } from '@/app/(main)/settings/profile/DateRangeSetting';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ export const labels = defineMessages({
|
|||
realtime: { id: 'label.realtime', defaultMessage: 'Realtime' },
|
||||
queries: { id: 'label.queries', defaultMessage: 'Queries' },
|
||||
teams: { id: 'label.teams', defaultMessage: 'Teams' },
|
||||
teamSettings: { id: 'label.team-settings', defaultMessage: 'Team settings' },
|
||||
analytics: { id: 'label.analytics', defaultMessage: 'Analytics' },
|
||||
login: { id: 'label.login', defaultMessage: 'Login' },
|
||||
logout: { id: 'label.logout', defaultMessage: 'Logout' },
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export { default as Profile } from './Profile';
|
|||
export { default as Pushpin } from './Pushpin';
|
||||
export { default as Redo } from './Redo';
|
||||
export { default as Reports } from './Reports';
|
||||
export { default as Security } from './Security';
|
||||
export { default as Speaker } from './Speaker';
|
||||
export { default as Sun } from './Sun';
|
||||
export { default as Tag } from './Tag';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue