Added menu options for cloud mode. Async fixes.

This commit is contained in:
Mike Cao 2025-09-23 09:14:01 -07:00
parent 4df6f06485
commit f639bb07f4
12 changed files with 47 additions and 27 deletions

View file

@ -17,7 +17,7 @@ export function PixelDeleteButton({
const { mutateAsync, isPending, error } = useDeleteQuery(`/pixels/${pixelId}`); const { mutateAsync, isPending, error } = useDeleteQuery(`/pixels/${pixelId}`);
const { touch } = useModified(); const { touch } = useModified();
const handleConfirm = (close: () => void) => { const handleConfirm = async (close: () => void) => {
await mutateAsync(null, { await mutateAsync(null, {
onSuccess: () => { onSuccess: () => {
touch('pixels'); touch('pixels');

View file

@ -9,7 +9,7 @@ export function PixelEditButton({ pixelId }: { pixelId: string }) {
return ( return (
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}> <ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
<Dialog title={formatMessage(labels.pixel)} style={{ width: 800, minHeight: 300 }}> <Dialog title={formatMessage(labels.pixel)} style={{ width: 600, minHeight: 300 }}>
{({ close }) => { {({ close }) => {
return <PixelEditForm pixelId={pixelId} onClose={close} />; return <PixelEditForm pixelId={pixelId} onClose={close} />;
}} }}

View file

@ -106,10 +106,7 @@ export function PixelEditForm({
allowCopy allowCopy
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
<Button <Button onPress={() => setValue('slug', handleSlug(), { shouldDirty: true })}>
variant="quiet"
onPress={() => setValue('slug', handleSlug(), { shouldDirty: true })}
>
<Icon> <Icon>
<RefreshCw /> <RefreshCw />
</Icon> </Icon>

View file

@ -1,7 +1,7 @@
import { usePixel, useMessages, useSlug } from '@/components/hooks'; import { usePixel, useMessages, useSlug } from '@/components/hooks';
import { PageHeader } from '@/components/common/PageHeader'; import { PageHeader } from '@/components/common/PageHeader';
import { Icon, Text } from '@umami/react-zen'; import { Icon, Text } from '@umami/react-zen';
import { ExternalLink, Pixel } from '@/components/icons'; import { ExternalLink, Grid2x2 } from '@/components/icons';
import { LinkButton } from '@/components/common/LinkButton'; import { LinkButton } from '@/components/common/LinkButton';
export function PixelHeader() { export function PixelHeader() {
@ -10,7 +10,7 @@ export function PixelHeader() {
const pixel = usePixel(); const pixel = usePixel();
return ( return (
<PageHeader title={pixel.name} description={pixel.url} icon={<Pixel />}> <PageHeader title={pixel.name} description={pixel.slug} icon={<Grid2x2 />}>
<LinkButton href={getSlugUrl(pixel.slug)} target="_blank"> <LinkButton href={getSlugUrl(pixel.slug)} target="_blank">
<Icon> <Icon>
<ExternalLink /> <ExternalLink />

View file

@ -1,8 +1,8 @@
import { PixelPage } from './PixelPage'; import { PixelPage } from './PixelPage';
import { Metadata } from 'next'; import { Metadata } from 'next';
export default async function ({ params }: { params: Promise<{ pixelId: string }> }) { export default function ({ params }: { params: { pixelId: string } }) {
const { pixelId } = await params; const { pixelId } = params;
return <PixelPage pixelId={pixelId} />; return <PixelPage pixelId={pixelId} />;
} }

View file

@ -4,7 +4,7 @@ import { Grid, Column } from '@umami/react-zen';
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { PageBody } from '@/components/common/PageBody'; import { PageBody } from '@/components/common/PageBody';
import { SideMenu } from '@/components/common/SideMenu'; import { SideMenu } from '@/components/common/SideMenu';
import { UserCircle, Users, Knobs } from '@/components/icons'; import { UserCircle, Users, Settings2 } from '@/components/icons';
export function SettingsLayout({ children }: { children: ReactNode }) { export function SettingsLayout({ children }: { children: ReactNode }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
@ -18,7 +18,7 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
id: 'preferences', id: 'preferences',
label: formatMessage(labels.preferences), label: formatMessage(labels.preferences),
path: renderUrl('/settings/preferences'), path: renderUrl('/settings/preferences'),
icon: <Knobs />, icon: <Settings2 />,
}, },
], ],
}, },

View file

@ -8,7 +8,7 @@ import {
Search, Search,
Type, Type,
SquareSlash, SquareSlash,
SquareArrowRight, Share2,
Megaphone, Megaphone,
Earth, Earth,
Globe, Globe,
@ -20,7 +20,7 @@ import {
Monitor, Monitor,
Cpu, Cpu,
LightningSvg, LightningSvg,
LucideCaseSensitive, Network,
Tag, Tag,
} from '@/components/icons'; } from '@/components/icons';
@ -80,7 +80,7 @@ export function WebsiteExpandedView({
id: 'referrer', id: 'referrer',
label: formatMessage(labels.referrer), label: formatMessage(labels.referrer),
path: updateParams({ view: 'referrer' }), path: updateParams({ view: 'referrer' }),
icon: <SquareArrowRight />, icon: <Share2 />,
}, },
{ {
id: 'channel', id: 'channel',
@ -167,7 +167,7 @@ export function WebsiteExpandedView({
id: 'hostname', id: 'hostname',
label: formatMessage(labels.hostname), label: formatMessage(labels.hostname),
path: updateParams({ view: 'hostname' }), path: updateParams({ view: 'hostname' }),
icon: <LucideCaseSensitive />, icon: <Network />,
}, },
{ {
id: 'tag', id: 'tag',

View file

@ -10,7 +10,7 @@ import {
MagnetSvg, MagnetSvg,
Tag, Tag,
MoneySvg, MoneySvg,
Network, NetworkSvg,
ChartPie, ChartPie,
UserPlus, UserPlus,
CompareSvg, CompareSvg,
@ -137,7 +137,7 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) {
{ {
id: 'attribution', id: 'attribution',
label: formatMessage(labels.attribution), label: formatMessage(labels.attribution),
icon: <Network />, icon: <NetworkSvg />,
path: renderPath('/attribution'), path: renderPath('/attribution'),
}, },
], ],

View file

@ -1,6 +1,7 @@
import { List, ListItem } from '@umami/react-zen'; import { List, ListItem } from '@umami/react-zen';
import { useWebsiteSegmentsQuery } from '@/components/hooks'; import { useWebsiteSegmentsQuery } from '@/components/hooks';
import { LoadingPanel } from '@/components/common/LoadingPanel'; import { LoadingPanel } from '@/components/common/LoadingPanel';
import { Empty } from '@/components/common/Empty';
export interface SegmentFiltersProps { export interface SegmentFiltersProps {
websiteId: string; websiteId: string;
@ -23,6 +24,7 @@ export function SegmentFilters({
return ( return (
<LoadingPanel data={data} isLoading={isLoading} isFetching={isFetching} overflowY="auto"> <LoadingPanel data={data} isLoading={isLoading} isFetching={isFetching} overflowY="auto">
{data?.data?.length === 0 && <Empty />}
<List selectionMode="single" value={[segmentId]} onChange={id => handleChange(id[0])}> <List selectionMode="single" value={[segmentId]} onChange={id => handleChange(id[0])}>
{data?.data?.map(item => { {data?.data?.map(item => {
return ( return (

View file

@ -10,23 +10,27 @@ import {
MenuSection, MenuSection,
} from '@umami/react-zen'; } from '@umami/react-zen';
import { useMessages, useLoginQuery, useNavigation, useConfig } from '@/components/hooks'; import { useMessages, useLoginQuery, useNavigation, useConfig } from '@/components/hooks';
import { LogOut, LockKeyhole, Settings, UserCircle } from '@/components/icons'; import { LogOut, LockKeyhole, Settings, UserCircle, LifeBuoy, BookText } from '@/components/icons';
import { DOCS_URL } from '@/lib/constants';
export function SettingsButton() { export function SettingsButton() {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { user } = useLoginQuery(); const { user } = useLoginQuery();
const { router, renderUrl } = useNavigation(); const { router } = useNavigation();
const { cloudMode } = useConfig(); const { cloudMode } = useConfig();
const handleAction = (id: Key) => { const handleAction = (id: Key) => {
const url = `/${id}`; const url = id.toString();
if (cloudMode) { if (cloudMode) {
window.location.href = url; if (url === '/docs') {
return; window.open(DOCS_URL, '_blank');
} else {
window.location.href = url;
}
} else {
router.push(url);
} }
router.push(renderUrl(url));
}; };
return ( return (
@ -40,12 +44,26 @@ export function SettingsButton() {
<Menu autoFocus="last" onAction={handleAction}> <Menu autoFocus="last" onAction={handleAction}>
<MenuSection title={user.username}> <MenuSection title={user.username}>
<MenuSeparator /> <MenuSeparator />
<MenuItem id="settings" icon={<Settings />} label={formatMessage(labels.settings)} /> <MenuItem id="/settings" icon={<Settings />} label={formatMessage(labels.settings)} />
{!cloudMode && user.isAdmin && ( {!cloudMode && user.isAdmin && (
<MenuItem id="admin" icon={<LockKeyhole />} label={formatMessage(labels.admin)} /> <MenuItem id="admin" icon={<LockKeyhole />} label={formatMessage(labels.admin)} />
)} )}
{cloudMode && (
<>
<MenuItem
id="/docs"
icon={<BookText />}
label={formatMessage(labels.documentation)}
/>
<MenuItem
id="/settings/support"
icon={<LifeBuoy />}
label={formatMessage(labels.support)}
/>
</>
)}
<MenuSeparator /> <MenuSeparator />
<MenuItem id="logout" icon={<LogOut />} label={formatMessage(labels.logout)} /> <MenuItem id="/logout" icon={<LogOut />} label={formatMessage(labels.logout)} />
</MenuSection> </MenuSection>
</Menu> </Menu>
</Popover> </Popover>

View file

@ -360,6 +360,8 @@ export const labels = defineMessages({
environment: { id: 'label.environment', defaultMessage: 'Environment' }, environment: { id: 'label.environment', defaultMessage: 'Environment' },
criteria: { id: 'label.criteria', defaultMessage: 'Criteria' }, criteria: { id: 'label.criteria', defaultMessage: 'Criteria' },
share: { id: 'label.share', defaultMessage: 'Share' }, share: { id: 'label.share', defaultMessage: 'Share' },
support: { id: 'label.support', defaultMessage: 'Support' },
documentation: { id: 'label.documentation', defaultMessage: 'Documentation' },
}); });
export const messages = defineMessages({ export const messages = defineMessages({

View file

@ -8,6 +8,7 @@ export const DASHBOARD_CONFIG = 'umami.dashboard';
export const VERSION_CHECK = 'umami.version-check'; export const VERSION_CHECK = 'umami.version-check';
export const SHARE_TOKEN_HEADER = 'x-umami-share-token'; export const SHARE_TOKEN_HEADER = 'x-umami-share-token';
export const HOMEPAGE_URL = 'https://umami.is'; export const HOMEPAGE_URL = 'https://umami.is';
export const DOCS_URL = 'https://umami.is/docs';
export const REPO_URL = 'https://github.com/umami-software/umami'; export const REPO_URL = 'https://github.com/umami-software/umami';
export const UPDATES_URL = 'https://api.umami.is/v1/updates'; export const UPDATES_URL = 'https://api.umami.is/v1/updates';
export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png'; export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png';