Updates for cloud mode.
Some checks failed
Node.js CI / build (postgresql, 18.18) (push) Has been cancelled

This commit is contained in:
Mike Cao 2025-09-04 20:27:42 -07:00
parent dc1736458b
commit f40e1b44f3
51 changed files with 251 additions and 173 deletions

View file

@ -10,7 +10,7 @@ export interface LoadingPanelProps extends ColumnProps {
isLoading?: boolean;
isFetching?: boolean;
loadingIcon?: 'dots' | 'spinner';
loadingPosition?: 'center' | 'page' | 'inline';
loadingPlacement?: 'center' | 'absolute' | 'inline';
renderEmpty?: () => ReactNode;
children: ReactNode;
}
@ -22,7 +22,7 @@ export function LoadingPanel({
isLoading,
isFetching,
loadingIcon = 'dots',
loadingPosition = 'page',
loadingPlacement = 'absolute',
renderEmpty = () => <Empty />,
children,
...props
@ -34,7 +34,7 @@ export function LoadingPanel({
{/* Show loading spinner only if no data exists */}
{(isLoading || isFetching) && (
<Column position="relative" height="100%" {...props}>
<Loading icon={loadingIcon} position={loadingPosition} />
<Loading icon={loadingIcon} placement={loadingPlacement} />
</Column>
)}

View file

@ -24,7 +24,7 @@ export function PageBody({
}
if (isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
return (

View file

@ -1,6 +1,7 @@
export {
AlertTriangle as Alert,
ArrowRight as Arrow,
Bookmark,
Calendar,
ChartPie,
ChevronRight as Chevron,
@ -50,4 +51,20 @@ export {
UserPlus,
X as Close,
} from 'lucide-react';
export * from '@/components/svg';
export {
Logo,
Bolt,
Change,
Compare,
Funnel,
Lightbulb,
Lightning,
Magnet,
Money,
Network,
Path,
Tag,
Target,
AddUser,
Visitor,
} from '@/components/svg';

View file

@ -1,12 +1,12 @@
import { useState, Key, Fragment } from 'react';
import { Modal, Select, ListItem, ListSeparator, Dialog, Row } from '@umami/react-zen';
import { Modal, Select, ListItem, ListSeparator, Dialog, SelectProps } from '@umami/react-zen';
import { endOfYear } from 'date-fns';
import { DatePickerForm } from '@/components/metrics/DatePickerForm';
import { useMessages } from '@/components/hooks';
import { DateDisplay } from '@/components/common/DateDisplay';
import { parseDateRange } from '@/lib/date';
export interface DateFilterProps {
export interface DateFilterProps extends SelectProps {
value?: string;
onChange?: (value: string) => void;
showAllTime?: boolean;
@ -20,6 +20,7 @@ export function DateFilter({
showAllTime,
renderDate,
placement = 'bottom',
...props
}: DateFilterProps) {
const { formatMessage, labels } = useMessages();
const [showPicker, setShowPicker] = useState(false);
@ -99,8 +100,9 @@ export function DateFilter({
};
return (
<Row minWidth="200px">
<>
<Select
{...props}
value={value}
placeholder={formatMessage(labels.selectDate)}
onChange={handleChange}
@ -130,6 +132,6 @@ export function DateFilter({
</Dialog>
</Modal>
)}
</Row>
</>
);
}

View file

@ -49,7 +49,7 @@ export function LookupField({ websiteId, type, value, onChange, ...props }: Look
allowsCustomValue
renderEmptyState={() =>
isLoading ? (
<Loading position="center" icon="dots" />
<Loading placement="center" icon="dots" />
) : (
<Empty message={formatMessage(messages.noResultsFound)} />
)

View file

@ -0,0 +1,27 @@
import { Button, Icon, DialogTrigger, Popover, Column, Label } from '@umami/react-zen';
import { TimezoneSetting } from '@/app/(main)/settings/preferences/TimezoneSetting';
import { DateRangeSetting } from '@/app/(main)/settings/preferences/DateRangeSetting';
import { Settings } from '@/components/icons';
import { useMessages } from '@/components/hooks';
export function PreferencesButton() {
const { formatMessage, labels } = useMessages();
return (
<DialogTrigger>
<Button variant="quiet">
<Icon>
<Settings />
</Icon>
</Button>
<Popover placement="bottom end">
<Column gap="3">
<Label>{formatMessage(labels.timezone)}</Label>
<TimezoneSetting />
<Label>{formatMessage(labels.defaultDateRange)}</Label>
<DateRangeSetting />
</Column>
</Popover>
</DialogTrigger>
);
}

View file

@ -11,31 +11,31 @@ import {
Text,
Row,
} from '@umami/react-zen';
import { useMessages, useLoginQuery, useNavigation } from '@/components/hooks';
import { useMessages, useLoginQuery, useNavigation, useConfig } from '@/components/hooks';
import { LogOut, UserCircle, LockKeyhole } from '@/components/icons';
export function ProfileButton() {
const { formatMessage, labels } = useMessages();
const { user } = useLoginQuery();
const { renderUrl } = useNavigation();
const cloudMode = !!process.env.cloudMode;
const { cloudUrl } = useConfig();
const items = [
{
id: 'profile',
id: 'settings',
label: formatMessage(labels.profile),
path: renderUrl('/settings/profile'),
icon: <UserCircle />,
},
user.isAdmin &&
!cloudMode && {
!cloudUrl && {
id: 'admin',
label: formatMessage(labels.admin),
path: '/admin',
icon: <LockKeyhole />,
},
{
id: 'LogOut',
id: 'logout',
label: formatMessage(labels.logout),
path: '/logout',
icon: <LogOut />,

View file

@ -1,27 +1,71 @@
import { Button, Icon, DialogTrigger, Popover, Column, Label } from '@umami/react-zen';
import { TimezoneSetting } from '@/app/(main)/settings/preferences/TimezoneSetting';
import { DateRangeSetting } from '@/app/(main)/settings/preferences/DateRangeSetting';
import { Gear } from '@/components/icons';
import { useMessages } from '@/components/hooks';
import { Key } from 'react';
import {
Icon,
Button,
MenuTrigger,
Popover,
Menu,
MenuItem,
MenuSeparator,
MenuSection,
Dialog,
SubMenuTrigger,
} from '@umami/react-zen';
import { useMessages, useLoginQuery, useNavigation, useConfig } from '@/components/hooks';
import { LogOut, LockKeyhole, Settings, Knobs } from '@/components/icons';
import { PreferenceSettings } from '@/app/(main)/settings/preferences/PreferenceSettings';
export function SettingsButton() {
const { formatMessage, labels } = useMessages();
const { user } = useLoginQuery();
const { router, renderUrl } = useNavigation();
const { cloudMode, cloudUrl } = useConfig();
const handleAction = (id: Key) => {
if (id === 'settings') {
if (cloudMode) {
window.location.href = `${cloudUrl}/dashboard`;
return;
}
}
router.push(renderUrl(`/${id}`));
};
return (
<DialogTrigger>
<Button variant="quiet">
<MenuTrigger>
<Button data-test="button-profile" variant="quiet" autoFocus={false}>
<Icon>
<Gear />
<Settings />
</Icon>
</Button>
<Popover placement="bottom end">
<Column gap="3">
<Label>{formatMessage(labels.timezone)}</Label>
<TimezoneSetting />
<Label>{formatMessage(labels.defaultDateRange)}</Label>
<DateRangeSetting />
</Column>
<Menu autoFocus="last" onAction={handleAction}>
<MenuSection title={user.username}>
<MenuSeparator />
<MenuItem id="settings" icon={<Settings />} label={formatMessage(labels.settings)} />
{cloudMode && (
<SubMenuTrigger>
<MenuItem
icon={<Knobs />}
label={formatMessage(labels.preferences)}
showSubMenuIcon
/>
<Popover placement="right bottom">
<Dialog>
<PreferenceSettings />
</Dialog>
</Popover>
</SubMenuTrigger>
)}
{!cloudMode && user.isAdmin && (
<MenuItem id="admin" icon={<LockKeyhole />} label={formatMessage(labels.admin)} />
)}
<MenuSeparator />
<MenuItem id="logout" icon={<LogOut />} label={formatMessage(labels.logout)} />
</MenuSection>
</Menu>
</Popover>
</DialogTrigger>
</MenuTrigger>
);
}

View file

@ -1,5 +1,3 @@
import { Key } from 'react';
import { useRouter } from 'next/navigation';
import {
Text,
Icon,
@ -10,8 +8,8 @@ import {
MenuSeparator,
Popover,
Row,
Box,
Button,
Column,
Pressable,
Loading,
} from '@umami/react-zen';
import { useLoginQuery, useMessages, useUserTeamsQuery, useNavigation } from '@/components/hooks';
@ -32,7 +30,7 @@ export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) {
const label = teamId ? team?.name : user.username;
if (isLoading) {
return <Loading icon="dots" position="center" />;
return <Loading icon="dots" size="sm" placement="center" />;
}
if (!data?.count) {
@ -41,8 +39,15 @@ export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) {
return (
<MenuTrigger>
<Button variant="outline">
<Row alignItems="center" justifyContent="space-between" flexGrow={1}>
<Pressable>
<Row
alignItems="center"
justifyContent="space-between"
flexGrow={1}
padding
backgroundColor="2"
style={{ cursor: 'pointer', textWrap: 'nowrap', outline: 'none' }}
>
<Row alignItems="center" gap>
<Icon>{teamId ? <Users /> : <User />}</Icon>
{showText && <Text>{label}</Text>}
@ -53,9 +58,9 @@ export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) {
</Icon>
)}
</Row>
</Button>
</Pressable>
<Popover placement="bottom start">
<Box minWidth="300px">
<Column minWidth="300px">
<Menu
selectionMode="single"
selectedKeys={selectedKeys}
@ -86,7 +91,7 @@ export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) {
))}
</MenuSection>
</Menu>
</Box>
</Column>
</Popover>
</MenuTrigger>
);

View file

@ -59,12 +59,14 @@ export function WebsiteDateFilter({
</Button>
</Row>
)}
<DateFilter
value={value}
onChange={handleChange}
showAllTime={showAllTime}
renderDate={+offset !== 0}
/>
<Row width="200px">
<DateFilter
value={value}
onChange={handleChange}
showAllTime={showAllTime}
renderDate={+offset !== 0}
/>
</Row>
{allowCompare && !isAllTime && (
<Row alignItems="center" gap>
<Text weight="bold">VS</Text>