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

@ -11,8 +11,8 @@ export function App({ children }) {
const config = useConfig();
const pathname = usePathname();
if (isLoading) {
return <Loading position="page" />;
if (isLoading || !config) {
return <Loading placement="absolute" />;
}
if (error) {

View file

@ -14,15 +14,13 @@ import {
Link as LinkIcon,
Logo,
Pixel,
Settings,
PanelLeft,
} from '@/components/icons';
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
import { TeamsButton } from '@/components/input/TeamsButton';
import { PanelButton } from '@/components/input/PanelButton';
import { ProfileButton } from '@/components/input/ProfileButton';
import { LanguageButton } from '@/components/input/LanguageButton';
import { Key } from 'react';
import { SettingsButton } from '@/components/input/SettingsButton';
export function SideNav(props: SidebarProps) {
const { formatMessage, labels } = useMessages();
@ -58,17 +56,7 @@ export function SideNav(props: SidebarProps) {
},
];
const bottomLinks = [
{
id: 'settings',
label: formatMessage(labels.settings),
path: renderUrl('/settings'),
icon: <Settings />,
},
];
const handleSelect = (id: Key) => {
console.log({ id });
router.push(id === 'user' ? '/websites' : `/teams/${id}/websites`);
};
@ -78,18 +66,15 @@ export function SideNav(props: SidebarProps) {
<SidebarSection onClick={() => setIsCollapsed(false)}>
<SidebarHeader
label="umami"
icon={
isCollapsed && !hasNav ? (
<PanelLeft />
) : (
<Logo onClick={() => (window.location.href = process.env.cloudUrl)} />
)
}
icon={isCollapsed && !hasNav ? <PanelLeft /> : <Logo />}
style={{ maxHeight: 40 }}
>
{!isCollapsed && !hasNav && <PanelButton />}
</SidebarHeader>
</SidebarSection>
<SidebarSection paddingTop="0" paddingBottom="0" justifyContent="center">
<TeamsButton showText={!hasNav && !isCollapsed} onAction={handleSelect} />
</SidebarSection>
<SidebarSection flexGrow={1}>
{links.map(({ id, path, label, icon }) => {
return (
@ -104,30 +89,10 @@ export function SideNav(props: SidebarProps) {
);
})}
</SidebarSection>
<SidebarSection style={{ paddingTop: 0, paddingBottom: 0 }}>
<TeamsButton showText={!hasNav && !isCollapsed} onAction={handleSelect} />
</SidebarSection>
<SidebarSection>
{bottomLinks.map(({ id, path, label, icon }) => {
return (
<Link key={id} href={path} role="button">
<SidebarItem
label={label}
icon={icon}
isSelected={pathname.includes(path)}
role="button"
/>
</Link>
);
})}
<Row alignItems="center" height="40px">
<ProfileButton />
{!isCollapsed && !hasNav && (
<Row>
<LanguageButton />
<ThemeButton />
</Row>
)}
<SidebarSection justifyContent="flex-start">
<Row>
<SettingsButton />
{!isCollapsed && !hasNav && <ThemeButton />}
</Row>
</SidebarSection>
</Sidebar>

View file

@ -18,7 +18,7 @@ export function UpdateNotice({ user, config }) {
!config?.updatesDisabled &&
!config?.privateMode &&
!pathname.includes('/share/') &&
!process.env.cloudMode &&
!process.env.cloudUrl &&
!dismissed;
const updateCheck = useCallback(() => {

View file

@ -11,7 +11,7 @@ export function AdminLayout({ children }: { children: ReactNode }) {
const { formatMessage, labels } = useMessages();
const { pathname } = useNavigation();
if (!user.isAdmin) {
if (!user.isAdmin || process.env.cloudUrl) {
return null;
}

View file

@ -2,7 +2,7 @@ import { Metadata } from 'next';
import { AdminLayout } from './AdminLayout';
export default function ({ children }) {
if (process.env.cloudMode) {
if (process.env.cloudUrl) {
return null;
}

View file

@ -9,7 +9,7 @@ export function UserProvider({ userId, children }: { userId: string; children: R
const { data: user, isFetching, isLoading } = useUserQuery(userId);
if (isFetching && isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
if (!user) {

View file

@ -78,7 +78,7 @@ export function LinkEditForm({
}, [data]);
if (linkId && isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
return (

View file

@ -10,7 +10,7 @@ export function LinkProvider({ linkId, children }: { linkId?: string; children:
const { data: link, isLoading, isFetching } = useLinkQuery(linkId);
if (isFetching && isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
if (!link) {

View file

@ -70,7 +70,7 @@ export function PixelEditForm({
}, [data]);
if (pixelId && isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
return (

View file

@ -10,7 +10,7 @@ export function PixelProvider({ pixelId, children }: { pixelId?: string; childre
const { data: pixel, isLoading, isFetching } = usePixelQuery(pixelId);
if (isFetching && isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
if (!pixel) {

View file

@ -2,6 +2,10 @@ import { Metadata } from 'next';
import { SettingsLayout } from './SettingsLayout';
export default function ({ children }) {
if (process.env.cloudUrl) {
return null;
}
return <SettingsLayout>{children}</SettingsLayout>;
}

View file

@ -15,7 +15,7 @@ export function DateRangeSetting() {
return (
<Row gap="3">
<DateFilter value={value} onChange={handleChange} />
<DateFilter value={value} onChange={handleChange} placement="bottom start" />
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
</Row>
);

View file

@ -26,7 +26,7 @@ export function LanguageSetting() {
};
return (
<Row gap="3">
<Row gap>
<Select
value={locale}
onChange={val => saveLocale(val as string)}

View file

@ -22,7 +22,7 @@ export function TimezoneSetting() {
};
return (
<Row gap="3">
<Row gap>
<Select
value={timezone}
onChange={(value: any) => saveTimezone(value)}

View file

@ -1,12 +1,12 @@
import { Row, Column, Label } from '@umami/react-zen';
import { useLoginQuery, useMessages } from '@/components/hooks';
import { useConfig, useLoginQuery, useMessages } from '@/components/hooks';
import { ROLES } from '@/lib/constants';
import { PasswordChangeButton } from './PasswordChangeButton';
export function ProfileSettings() {
const { user } = useLoginQuery();
const { formatMessage, labels } = useMessages();
const cloudMode = !!process.env.cloudMode;
const { cloudMode } = useConfig();
if (!user) {
return null;

View file

@ -10,7 +10,7 @@ export function TeamProvider({ teamId, children }: { teamId?: string; children:
const { data: team, isLoading, isFetching } = useTeamQuery(teamId);
if (isFetching && isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
if (!team) {

View file

@ -1,10 +1,10 @@
import { ReactNode } from 'react';
import Link from 'next/link';
import { DataGrid } from '@/components/common/DataGrid';
import { TeamsTable } from './TeamsTable';
import { useLoginQuery, useUserTeamsQuery } from '@/components/hooks';
import { ReactNode } from 'react';
export function TeamsDataTable({
allowEdit,
showActions,
}: {
allowEdit?: boolean;
@ -14,10 +14,18 @@ export function TeamsDataTable({
const { user } = useLoginQuery();
const query = useUserTeamsQuery(user.id);
const renderLink = (row: any) => {
return (
<Link key={row.id} href={`/teams/${row.id}`}>
{row.name}
</Link>
);
};
return (
<DataGrid query={query}>
{({ data }) => {
return <TeamsTable data={data} allowEdit={allowEdit} showActions={showActions} />;
return <TeamsTable data={data} showActions={showActions} renderLink={renderLink} />;
}}
</DataGrid>
);

View file

@ -1,14 +1,14 @@
import { Row } from '@umami/react-zen';
import { PageHeader } from '@/components/common/PageHeader';
import { ROLES } from '@/lib/constants';
import { useLoginQuery, useMessages } from '@/components/hooks';
import { useConfig, useLoginQuery, useMessages } from '@/components/hooks';
import { TeamsJoinButton } from './TeamsJoinButton';
import { TeamsAddButton } from './TeamsAddButton';
export function TeamsHeader({ allowCreate = true }: { allowCreate?: boolean }) {
const { formatMessage, labels } = useMessages();
const { user } = useLoginQuery();
const cloudMode = !!process.env.cloudMode;
const { cloudMode } = useConfig();
return (
<PageHeader title={formatMessage(labels.teams)}>

View file

@ -1,25 +1,25 @@
import { DataColumn, DataTable, Icon, MenuItem, Text, Row } from '@umami/react-zen';
import { useMessages, useNavigation } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import { Eye, Edit } from '@/components/icons';
import { ROLES } from '@/lib/constants';
import { MenuButton } from '@/components/input/MenuButton';
import Link from 'next/link';
import { ReactNode } from 'react';
export function TeamsTable({
data = [],
showActions = false,
renderLink,
}: {
data: any[];
allowEdit?: boolean;
showActions?: boolean;
renderLink?: (row: any) => ReactNode;
}) {
const { formatMessage, labels } = useMessages();
const { renderUrl } = useNavigation();
return (
<DataTable data={data}>
<DataColumn id="name" label={formatMessage(labels.name)}>
{(row: any) => <Link href={renderUrl(`/settings/teams/${row.id}`)}>{row.name}</Link>}
{renderLink}
</DataColumn>
<DataColumn id="owner" label={formatMessage(labels.owner)}>
{(row: any) => row?.members?.find(({ role }) => role === ROLES.teamOwner)?.user?.username}

View file

@ -16,7 +16,7 @@ export function WebsiteProvider({
const { data: website, isFetching, isLoading } = useWebsiteQuery(websiteId);
if (isFetching && isLoading) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
if (!website) {

View file

@ -1,6 +1,7 @@
import Link from 'next/link';
import { WebsitesTable } from './WebsitesTable';
import { DataGrid } from '@/components/common/DataGrid';
import { useUserWebsitesQuery } from '@/components/hooks';
import { useNavigation, useUserWebsitesQuery } from '@/components/hooks';
export function WebsitesDataTable({
teamId,
@ -14,16 +15,21 @@ export function WebsitesDataTable({
showActions?: boolean;
}) {
const queryResult = useUserWebsitesQuery({ teamId });
const { renderUrl } = useNavigation();
const renderLink = (row: any) => (
<Link href={renderUrl(`/websites/${row.id}`, false)}>{row.name}</Link>
);
return (
<DataGrid query={queryResult} allowSearch allowPaging>
{({ data }) => (
<WebsitesTable
teamId={teamId}
data={data}
showActions={showActions}
allowEdit={allowEdit}
allowView={allowView}
renderLink={renderLink}
/>
)}
</DataGrid>

View file

@ -3,27 +3,24 @@ import { Row, Text, Icon, DataTable, DataColumn, MenuItem } from '@umami/react-z
import { useMessages, useNavigation } from '@/components/hooks';
import { MenuButton } from '@/components/input/MenuButton';
import { Eye, SquarePen } from '@/components/icons';
import Link from 'next/link';
export interface WebsitesTableProps {
data: Record<string, any>[];
showActions?: boolean;
allowEdit?: boolean;
allowView?: boolean;
teamId?: string;
children?: ReactNode;
}
export function WebsitesTable({
data = [],
showActions,
allowEdit,
allowView,
renderLink,
children,
}: WebsitesTableProps) {
}: {
data: Record<string, any>[];
showActions?: boolean;
allowEdit?: boolean;
allowView?: boolean;
renderLink?: (row: any) => ReactNode;
children?: ReactNode;
}) {
const { formatMessage, labels } = useMessages();
const { renderUrl, pathname } = useNavigation();
const isSettings = pathname.includes('/settings');
const { renderUrl } = useNavigation();
if (!data?.length) {
return children;
@ -32,11 +29,7 @@ export function WebsitesTable({
return (
<DataTable data={data}>
<DataColumn id="name" label={formatMessage(labels.name)}>
{(row: any) => (
<Link href={renderUrl(`${isSettings ? '/settings' : ''}/websites/${row.id}`, false)}>
{row.name}
</Link>
)}
{renderLink}
</DataColumn>
<DataColumn id="domain" label={formatMessage(labels.domain)} />
{showActions && (

View file

@ -50,7 +50,7 @@ export function FunnelEditForm({
};
if (id && !data) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
const defaultValues = {

View file

@ -44,7 +44,7 @@ export function GoalEditForm({
};
if (id && !data) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
const defaultValues = {

View file

@ -52,7 +52,7 @@ export function CohortEditForm({
};
if (cohortId && !data) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
const defaultValues = {

View file

@ -49,7 +49,7 @@ export function SegmentEditForm({
};
if (segmentId && !data) {
return <Loading position="page" />;
return <Loading placement="absolute" />;
}
return (

View file

@ -64,7 +64,7 @@ export function WebsiteTransferForm({
};
if (isLoading) {
return <Loading icon="dots" position="center" />;
return <Loading icon="dots" placement="center" />;
}
return (

View file

@ -1,23 +1,27 @@
'use server';
export type Config = {
cloudMode: boolean;
cloudUrl?: string;
faviconUrl?: string;
linksUrl?: string;
pixelsUrl?: string;
privateMode: boolean;
telemetryDisabled: boolean;
trackerScriptName?: string;
updatesDisabled: boolean;
linksUrl?: string;
pixelsUrl?: string;
};
export async function getConfig(): Promise<Config> {
return {
cloudMode: !!process.env.CLOUD_URL,
cloudUrl: process.env.CLOUD_URL,
faviconUrl: process.env.FAVICON_URL,
linksUrl: process.env.LINKS_URL,
pixelsUrl: process.env.PIXELS_URL,
privateMode: !!process.env.PRIVATE_MODE,
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
updatesDisabled: !!process.env.DISABLE_UPDATES,
linksUrl: process.env.LINKS_URL,
pixelsUrl: process.env.PIXELS_URL,
};
}

View file

@ -8,7 +8,7 @@ import { removeClientAuthToken } from '@/lib/client';
export function LogoutPage() {
const router = useRouter();
const { post } = useApi();
const disabled = process.env.cloudMode;
const disabled = process.env.cloudUrl;
useEffect(() => {
async function logout() {

View file

@ -1,7 +1,7 @@
import { Row, Icon, Text, ThemeButton } from '@umami/react-zen';
import Link from 'next/link';
import { LanguageButton } from '@/components/input/LanguageButton';
import { SettingsButton } from '@/components/input/SettingsButton';
import { PreferencesButton } from '@/components/input/PreferencesButton';
import { Logo } from '@/components/icons';
export function Header() {
@ -18,7 +18,7 @@ export function Header() {
<Row alignItems="center" gap>
<ThemeButton />
<LanguageButton />
<SettingsButton />
<PreferencesButton />
</Row>
</Row>
);

View file

@ -18,5 +18,5 @@ export function SSOPage() {
}
}, [router, url, token]);
return <Loading position="page" />;
return <Loading placement="absolute" />;
}