mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Small fixes.
This commit is contained in:
parent
5536e0b7e7
commit
efd4f4ca00
31 changed files with 621 additions and 586 deletions
24
package.json
24
package.json
|
|
@ -69,13 +69,13 @@
|
||||||
"@date-fns/utc": "^1.2.0",
|
"@date-fns/utc": "^1.2.0",
|
||||||
"@dicebear/collection": "^9.2.2",
|
"@dicebear/collection": "^9.2.2",
|
||||||
"@dicebear/core": "^9.2.2",
|
"@dicebear/core": "^9.2.2",
|
||||||
"@fontsource/inter": "^4.5.15",
|
"@fontsource/inter": "^5.2.5",
|
||||||
"@hello-pangea/dnd": "^17.0.0",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@internationalized/date": "^3.7.0",
|
"@internationalized/date": "^3.7.0",
|
||||||
"@prisma/client": "6.4.1",
|
"@prisma/client": "6.5.0",
|
||||||
"@prisma/extension-read-replicas": "^0.4.0",
|
"@prisma/extension-read-replicas": "^0.4.0",
|
||||||
"@react-spring/web": "^9.7.5",
|
"@react-spring/web": "^9.7.5",
|
||||||
"@tanstack/react-query": "^5.66.11",
|
"@tanstack/react-query": "^5.68.0",
|
||||||
"@umami/prisma-client": "^0.16.0",
|
"@umami/prisma-client": "^0.16.0",
|
||||||
"@umami/react-zen": "^0.63.0",
|
"@umami/react-zen": "^0.63.0",
|
||||||
"@umami/redis-client": "^0.27.0",
|
"@umami/redis-client": "^0.27.0",
|
||||||
|
|
@ -106,16 +106,16 @@
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"maxmind": "^4.3.24",
|
"maxmind": "^4.3.24",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"next": "15.2.1",
|
"next": "15.2.2",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prisma": "6.4.1",
|
"prisma": "6.5.0",
|
||||||
"pure-rand": "^6.1.0",
|
"pure-rand": "^6.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-aria-components": "^1.6.0",
|
"react-aria-components": "^1.6.0",
|
||||||
"react-basics": "^0.126.0",
|
"react-basics": "^0.126.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-error-boundary": "^4.1.2",
|
"react-error-boundary": "^5.0.0",
|
||||||
"react-intl": "^7.1.6",
|
"react-intl": "^7.1.6",
|
||||||
"react-simple-maps": "^2.3.0",
|
"react-simple-maps": "^2.3.0",
|
||||||
"react-use-measure": "^2.1.7",
|
"react-use-measure": "^2.1.7",
|
||||||
|
|
@ -126,11 +126,11 @@
|
||||||
"thenby": "^1.3.4",
|
"thenby": "^1.3.4",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"zod": "^3.24.2",
|
"zod": "^3.24.2",
|
||||||
"zustand": "^4.5.6"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@formatjs/cli": "^4.8.4",
|
"@formatjs/cli": "^4.8.4",
|
||||||
"@netlify/plugin-nextjs": "^5.9.4",
|
"@netlify/plugin-nextjs": "^5.10.0",
|
||||||
"@rollup/plugin-alias": "^5.1.1",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-commonjs": "^25.0.8",
|
"@rollup/plugin-commonjs": "^25.0.8",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
|
@ -145,11 +145,11 @@
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/react-window": "^1.8.8",
|
"@types/react-window": "^1.8.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@typescript-eslint/parser": "^8.26.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.17.0",
|
"cypress": "^13.17.0",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.1",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-next": "^14.2.24",
|
"eslint-config-next": "^14.2.24",
|
||||||
"eslint-config-prettier": "^8.10.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
|
|
|
||||||
796
pnpm-lock.yaml
generated
796
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -3,10 +3,10 @@ import { Grid, Loading } from '@umami/react-zen';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import { UpdateNotice } from './UpdateNotice';
|
import { UpdateNotice } from './UpdateNotice';
|
||||||
|
import { Nav } from '@/app/(main)/Nav';
|
||||||
import { NavBar } from '@/app/(main)/NavBar';
|
import { NavBar } from '@/app/(main)/NavBar';
|
||||||
import { Page } from '@/components/layout/Page';
|
import { Page } from '@/components/layout/Page';
|
||||||
import { useLogin, useConfig } from '@/components/hooks';
|
import { useLogin, useConfig } from '@/components/hooks';
|
||||||
import { Nav } from '@/app/(main)/Nav';
|
|
||||||
|
|
||||||
export function App({ children }) {
|
export function App({ children }) {
|
||||||
const { user, isLoading, error } = useLogin();
|
const { user, isLoading, error } = useLogin();
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,9 @@ export function Nav() {
|
||||||
].filter(n => n);
|
].filter(n => n);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SideNav isCollapsed={isCollapsed}>
|
<SideNav isCollapsed={isCollapsed} variant="3">
|
||||||
<SideNavSection>
|
<SideNavSection>
|
||||||
<SideNavHeader name="umami" icon={<Icons.Logo />} />
|
<SideNavHeader label="umami" icon={<Icons.Logo />} />
|
||||||
</SideNavSection>
|
</SideNavSection>
|
||||||
<SideNavSection>
|
<SideNavSection>
|
||||||
{links.map(({ href, label, icon }) => {
|
{links.map(({ href, label, icon }) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
import { Board } from './Board';
|
import { Column, Heading } from '@umami/react-zen';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export function BoardsPage() {
|
export function BoardsPage() {
|
||||||
return <Board />;
|
return (
|
||||||
|
<Column>
|
||||||
|
<Heading>My Boards</Heading>
|
||||||
|
<Link href="/teams/3a97e34a-7f9d-4de2-8754-ed81714b528d/boards/86d4095c-a2a8-4fc8-9521-103e858e2b41">
|
||||||
|
Board 1
|
||||||
|
</Link>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { Column, Heading } from '@umami/react-zen';
|
import { Column, Heading } from '@umami/react-zen';
|
||||||
|
|
||||||
export function Board() {
|
export function Board({ boardId }: { boardId: string }) {
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<Heading>Board title</Heading>
|
<Heading>Board title</Heading>
|
||||||
|
<div>{boardId}</div>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
12
src/app/(main)/boards/[boardId]/page.tsx
Normal file
12
src/app/(main)/boards/[boardId]/page.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { Board } from './Board';
|
||||||
|
|
||||||
|
export default async function ({ params }: { params: Promise<{ boardId: string }> }) {
|
||||||
|
const { boardId } = await params;
|
||||||
|
|
||||||
|
return <Board boardId={boardId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Board',
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { BoardsPage } from './BoardsPage';
|
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
import { BoardsPage } from './BoardsPage';
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return <BoardsPage />;
|
return <BoardsPage />;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function DateRangeSetting() {
|
||||||
endDate={dateRange.endDate}
|
endDate={dateRange.endDate}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleReset}>{formatMessage(labels.reset)}</Button>
|
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,16 @@ export function LanguageSetting() {
|
||||||
|
|
||||||
const handleReset = () => saveLocale(DEFAULT_LOCALE);
|
const handleReset = () => saveLocale(DEFAULT_LOCALE);
|
||||||
|
|
||||||
|
console.log({ options });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flexbox gap={10}>
|
<Flexbox gap={10}>
|
||||||
<Select
|
<Select value={locale} onChange={val => saveLocale(val as string)}>
|
||||||
items={options}
|
{options.map(item => (
|
||||||
value={locale}
|
<ListItem key={item} id={item}>
|
||||||
onChange={val => saveLocale(val as string)}
|
{languages[item].label}
|
||||||
allowSearch={true}
|
</ListItem>
|
||||||
onSearch={setSearch}
|
))}
|
||||||
menuProps={{ className: styles.menu }}
|
|
||||||
>
|
|
||||||
{item => <ListItem key={item}>{languages[item].label}</ListItem>}
|
|
||||||
</Select>
|
</Select>
|
||||||
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useRef } from 'react';
|
import { Form, FormField, FormButtons, PasswordField, Button } from '@umami/react-zen';
|
||||||
import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from '@umami/react-zen';
|
|
||||||
import { useApi, useMessages } from '@/components/hooks';
|
import { useApi, useMessages } from '@/components/hooks';
|
||||||
|
|
||||||
export function PasswordEditForm({ onSave, onClose }) {
|
export function PasswordEditForm({ onSave, onClose }) {
|
||||||
|
|
@ -8,7 +7,6 @@ export function PasswordEditForm({ onSave, onClose }) {
|
||||||
const { mutate, error, isPending } = useMutation({
|
const { mutate, error, isPending } = useMutation({
|
||||||
mutationFn: (data: any) => post('/me/password', data),
|
mutationFn: (data: any) => post('/me/password', data),
|
||||||
});
|
});
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
const handleSubmit = async (data: any) => {
|
const handleSubmit = async (data: any) => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
|
|
@ -19,48 +17,48 @@ export function PasswordEditForm({ onSave, onClose }) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const samePassword = (value: string) => {
|
const samePassword = (value: string, values: { [key: string]: any }) => {
|
||||||
if (value !== ref?.current?.getValues('newPassword')) {
|
if (value !== values.newPassword) {
|
||||||
return formatMessage(messages.noMatchPassword);
|
return formatMessage(messages.noMatchPassword);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form ref={ref} onSubmit={handleSubmit} error={error}>
|
<Form onSubmit={handleSubmit} error={error}>
|
||||||
<FormRow label={formatMessage(labels.currentPassword)}>
|
<FormField
|
||||||
<FormInput name="currentPassword" rules={{ required: 'Required' }}>
|
label={formatMessage(labels.currentPassword)}
|
||||||
<PasswordField autoComplete="current-password" />
|
name="currentPassword"
|
||||||
</FormInput>
|
rules={{ required: 'Required' }}
|
||||||
</FormRow>
|
>
|
||||||
<FormRow label={formatMessage(labels.newPassword)}>
|
<PasswordField autoComplete="current-password" />
|
||||||
<FormInput
|
</FormField>
|
||||||
name="newPassword"
|
<FormField
|
||||||
rules={{
|
name="newPassword"
|
||||||
required: 'Required',
|
label={formatMessage(labels.newPassword)}
|
||||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
|
rules={{
|
||||||
}}
|
required: 'Required',
|
||||||
>
|
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
|
||||||
<PasswordField autoComplete="new-password" />
|
}}
|
||||||
</FormInput>
|
>
|
||||||
</FormRow>
|
<PasswordField autoComplete="new-password" />
|
||||||
<FormRow label={formatMessage(labels.confirmPassword)}>
|
</FormField>
|
||||||
<FormInput
|
<FormField
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
rules={{
|
label={formatMessage(labels.confirmPassword)}
|
||||||
required: formatMessage(labels.required),
|
rules={{
|
||||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
|
required: formatMessage(labels.required),
|
||||||
validate: samePassword,
|
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
|
||||||
}}
|
validate: samePassword,
|
||||||
>
|
}}
|
||||||
<PasswordField autoComplete="confirm-password" />
|
>
|
||||||
</FormInput>
|
<PasswordField autoComplete="confirm-password" />
|
||||||
</FormRow>
|
</FormField>
|
||||||
<FormButtons flex>
|
<FormButtons>
|
||||||
<Button type="submit" variant="primary" disabled={isPending}>
|
<Button type="submit" variant="primary" isDisabled={isPending}>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
|
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Form, FormRow } from '@umami/react-zen';
|
import { Form, FormField, Column, Label } from '@umami/react-zen';
|
||||||
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
|
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
|
||||||
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
|
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
|
||||||
import { LanguageSetting } from '@/app/(main)/profile/LanguageSetting';
|
import { LanguageSetting } from '@/app/(main)/profile/LanguageSetting';
|
||||||
|
|
@ -33,26 +33,43 @@ export function ProfileSettings() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Column gap="6">
|
||||||
<FormRow label={formatMessage(labels.username)}>{username}</FormRow>
|
<Column>
|
||||||
<FormRow label={formatMessage(labels.role)}>{renderRole(role)}</FormRow>
|
<Label>{formatMessage(labels.username)}</Label>
|
||||||
|
{username}
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column>
|
||||||
|
<Label>{formatMessage(labels.role)}</Label>
|
||||||
|
{renderRole(role)}
|
||||||
|
</Column>
|
||||||
|
|
||||||
{!cloudMode && (
|
{!cloudMode && (
|
||||||
<FormRow label={formatMessage(labels.password)}>
|
<Column>
|
||||||
|
<Label>{formatMessage(labels.password)}</Label>
|
||||||
<PasswordChangeButton />
|
<PasswordChangeButton />
|
||||||
</FormRow>
|
</Column>
|
||||||
)}
|
)}
|
||||||
<FormRow label={formatMessage(labels.defaultDateRange)}>
|
|
||||||
|
<Column>
|
||||||
|
<Label>{formatMessage(labels.defaultDateRange)}</Label>
|
||||||
<DateRangeSetting />
|
<DateRangeSetting />
|
||||||
</FormRow>
|
</Column>
|
||||||
<FormRow label={formatMessage(labels.language)}>
|
|
||||||
|
<Column>
|
||||||
|
<Label>{formatMessage(labels.language)}</Label>
|
||||||
<LanguageSetting />
|
<LanguageSetting />
|
||||||
</FormRow>
|
</Column>
|
||||||
<FormRow label={formatMessage(labels.timezone)}>
|
|
||||||
|
<Column>
|
||||||
|
<Label>{formatMessage(labels.timezone)}</Label>
|
||||||
<TimezoneSetting />
|
<TimezoneSetting />
|
||||||
</FormRow>
|
</Column>
|
||||||
<FormRow label={formatMessage(labels.theme)}>
|
|
||||||
|
<Column>
|
||||||
|
<Label>{formatMessage(labels.theme)}</Label>
|
||||||
<ThemeSetting />
|
<ThemeSetting />
|
||||||
</FormRow>
|
</Column>
|
||||||
</Form>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function ThemeSetting() {
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<Button
|
<Button
|
||||||
className={classNames({ [styles.active]: theme === 'light' })}
|
className={classNames({ [styles.active]: theme === 'light' })}
|
||||||
onClick={() => saveTheme('light')}
|
onPress={() => saveTheme('light')}
|
||||||
>
|
>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Sun />
|
<Icons.Sun />
|
||||||
|
|
@ -19,7 +19,7 @@ export function ThemeSetting() {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className={classNames({ [styles.active]: theme === 'dark' })}
|
className={classNames({ [styles.active]: theme === 'dark' })}
|
||||||
onClick={() => saveTheme('dark')}
|
onPress={() => saveTheme('dark')}
|
||||||
>
|
>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Moon />
|
<Icons.Moon />
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export function TimezoneSetting() {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { timezone, saveTimezone } = useTimezone();
|
const { timezone, saveTimezone } = useTimezone();
|
||||||
const options = search
|
const items = search
|
||||||
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
|
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
|
||||||
: timezones;
|
: timezones;
|
||||||
|
|
||||||
|
|
@ -20,13 +20,13 @@ export function TimezoneSetting() {
|
||||||
<Row gap="3">
|
<Row gap="3">
|
||||||
<Select
|
<Select
|
||||||
className={styles.dropdown}
|
className={styles.dropdown}
|
||||||
items={options}
|
items={items}
|
||||||
value={timezone}
|
value={timezone}
|
||||||
onChange={(value: any) => saveTimezone(value)}
|
onChange={(value: any) => saveTimezone(value)}
|
||||||
allowSearch={true}
|
allowSearch={true}
|
||||||
onSearch={setSearch}
|
onSearch={setSearch}
|
||||||
>
|
>
|
||||||
{item => (
|
{(item: any) => (
|
||||||
<ListItem key={item} id={item}>
|
<ListItem key={item} id={item}>
|
||||||
{item}
|
{item}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ export function ReportHeader({ icon }) {
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
isLoading={isCreating || isUpdating}
|
isLoading={isCreating || isUpdating}
|
||||||
disabled={!websiteId || !dateRange?.value || !name}
|
isDisabled={!websiteId || !dateRange?.value || !name}
|
||||||
onPress={handleSave}
|
onPress={handleSave}
|
||||||
>
|
>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormRow,
|
FormField,
|
||||||
FormButtons,
|
FormButtons,
|
||||||
SubmitButton,
|
FormSubmitButton,
|
||||||
PopupTrigger,
|
DialogTrigger,
|
||||||
Icon,
|
Icon,
|
||||||
Popup,
|
Popover,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { Empty } from '@/components/common/Empty';
|
import { Empty } from '@/components/common/Empty';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
|
|
@ -75,12 +75,12 @@ export function EventDataParameters() {
|
||||||
|
|
||||||
const AddButton = ({ group, onAdd }) => {
|
const AddButton = ({ group, onAdd }) => {
|
||||||
return (
|
return (
|
||||||
<PopupTrigger>
|
<DialogTrigger>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Plus />
|
<Icons.Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
<Popup position="bottom" alignment="start">
|
<Popover placement="bottom start">
|
||||||
{(close: () => void) => {
|
{({ close }: any) => {
|
||||||
return (
|
return (
|
||||||
<FieldAddForm
|
<FieldAddForm
|
||||||
fields={data.map(({ dataKey, eventDataType }) => ({
|
fields={data.map(({ dataKey, eventDataType }) => ({
|
||||||
|
|
@ -93,8 +93,8 @@ export function EventDataParameters() {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Popup>
|
</Popover>
|
||||||
</PopupTrigger>
|
</DialogTrigger>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -106,11 +106,7 @@ export function EventDataParameters() {
|
||||||
hasData &&
|
hasData &&
|
||||||
parameterGroups.map(({ label, group }) => {
|
parameterGroups.map(({ label, group }) => {
|
||||||
return (
|
return (
|
||||||
<FormRow
|
<FormField name={label} key={label} label={label}>
|
||||||
key={label}
|
|
||||||
label={label}
|
|
||||||
action={<AddButton group={group} onAdd={handleAdd} />}
|
|
||||||
>
|
|
||||||
<ParameterList>
|
<ParameterList>
|
||||||
{parameterData[group].map(({ name, value }) => {
|
{parameterData[group].map(({ name, value }) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -134,13 +130,14 @@ export function EventDataParameters() {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ParameterList>
|
</ParameterList>
|
||||||
</FormRow>
|
<AddButton group={group} onAdd={handleAdd} />
|
||||||
|
</FormField>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<SubmitButton variant="primary" disabled={!queryEnabled} isLoading={isRunning}>
|
<FormSubmitButton variant="primary" isDisabled={!queryEnabled} isLoading={isRunning}>
|
||||||
{formatMessage(labels.runQuery)}
|
{formatMessage(labels.runQuery)}
|
||||||
</SubmitButton>
|
</FormSubmitButton>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ import {
|
||||||
Icon,
|
Icon,
|
||||||
Form,
|
Form,
|
||||||
FormButtons,
|
FormButtons,
|
||||||
FormInput,
|
FormField,
|
||||||
FormRow,
|
DialogTrigger,
|
||||||
PopupTrigger,
|
Popover,
|
||||||
Popup,
|
FormSubmitButton,
|
||||||
SubmitButton,
|
|
||||||
TextField,
|
TextField,
|
||||||
Button,
|
Button,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
|
|
@ -60,37 +59,36 @@ export function FunnelParameters() {
|
||||||
|
|
||||||
const AddStepButton = () => {
|
const AddStepButton = () => {
|
||||||
return (
|
return (
|
||||||
<PopupTrigger>
|
<DialogTrigger>
|
||||||
<Button>
|
<Button>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Icons.Plus />
|
<Icons.Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popup alignment="start">
|
<Popover placement="start">
|
||||||
<PopupForm>
|
<PopupForm>
|
||||||
<FunnelStepAddForm onChange={handleAddStep} />
|
<FunnelStepAddForm onChange={handleAddStep} />
|
||||||
</PopupForm>
|
</PopupForm>
|
||||||
</Popup>
|
</Popover>
|
||||||
</PopupTrigger>
|
</DialogTrigger>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
||||||
<BaseParameters allowWebsiteSelect={!id} />
|
<BaseParameters allowWebsiteSelect={!id} />
|
||||||
<FormRow label={formatMessage(labels.window)}>
|
<FormField
|
||||||
<FormInput
|
label={formatMessage(labels.window)}
|
||||||
name="window"
|
name="window"
|
||||||
rules={{ required: formatMessage(labels.required), pattern: /[0-9]+/ }}
|
rules={{ required: formatMessage(labels.required), pattern: /[0-9]+/ }}
|
||||||
>
|
>
|
||||||
<TextField autoComplete="off" />
|
<TextField autoComplete="off" />
|
||||||
</FormInput>
|
</FormField>
|
||||||
</FormRow>
|
<FormField name="steps" label={formatMessage(labels.steps)}>
|
||||||
<FormRow label={formatMessage(labels.steps)} action={<AddStepButton />}>
|
|
||||||
<ParameterList>
|
<ParameterList>
|
||||||
{steps.map((step: { type: string; value: string }, index: number) => {
|
{steps.map((step: { type: string; value: string }, index: number) => {
|
||||||
return (
|
return (
|
||||||
<PopupTrigger key={index}>
|
<DialogTrigger key={index}>
|
||||||
<ParameterList.Item
|
<ParameterList.Item
|
||||||
className={styles.item}
|
className={styles.item}
|
||||||
icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
|
icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
|
||||||
|
|
@ -100,8 +98,8 @@ export function FunnelParameters() {
|
||||||
<div>{step.value}</div>
|
<div>{step.value}</div>
|
||||||
</div>
|
</div>
|
||||||
</ParameterList.Item>
|
</ParameterList.Item>
|
||||||
<Popup alignment="start">
|
<Popover placement="start">
|
||||||
{(close: () => void) => (
|
{({ close }: any) => (
|
||||||
<PopupForm>
|
<PopupForm>
|
||||||
<FunnelStepAddForm
|
<FunnelStepAddForm
|
||||||
type={step.type}
|
type={step.type}
|
||||||
|
|
@ -110,16 +108,17 @@ export function FunnelParameters() {
|
||||||
/>
|
/>
|
||||||
</PopupForm>
|
</PopupForm>
|
||||||
)}
|
)}
|
||||||
</Popup>
|
</Popover>
|
||||||
</PopupTrigger>
|
</DialogTrigger>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ParameterList>
|
</ParameterList>
|
||||||
</FormRow>
|
<AddStepButton />
|
||||||
|
</FormField>
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
|
<FormSubmitButton variant="primary" isDisabled={queryDisabled} isLoading={isRunning}>
|
||||||
{formatMessage(labels.runQuery)}
|
{formatMessage(labels.runQuery)}
|
||||||
</SubmitButton>
|
</FormSubmitButton>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ export function JourneyView() {
|
||||||
<div className={styles.name} title={name}>
|
<div className={styles.name} title={name}>
|
||||||
<TextOverflow> {name}</TextOverflow>
|
<TextOverflow> {name}</TextOverflow>
|
||||||
</div>
|
</div>
|
||||||
<TooltipPopup label={dropOffPercent} disabled={!selected}>
|
<TooltipPopup label={dropOffPercent} isDisabled={!selected}>
|
||||||
<div className={styles.count} title={nodeCount}>
|
<div className={styles.count} title={nodeCount}>
|
||||||
{formatLongNumber(nodeCount)}
|
{formatLongNumber(nodeCount)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function RetentionParameters() {
|
||||||
<BaseParameters showDateSelect={false} allowWebsiteSelect={!id} />
|
<BaseParameters showDateSelect={false} allowWebsiteSelect={!id} />
|
||||||
|
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<FormSubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
|
<FormSubmitButton variant="primary" isDisabled={queryDisabled} isLoading={isRunning}>
|
||||||
{formatMessage(labels.runQuery)}
|
{formatMessage(labels.runQuery)}
|
||||||
</FormSubmitButton>
|
</FormSubmitButton>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { Form, FormButtons, SubmitButton } from '@umami/react-zen';
|
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
import { ReportContext } from '../[reportId]/Report';
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
|
|
||||||
|
|
@ -25,9 +25,9 @@ export function UTMParameters() {
|
||||||
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
||||||
<BaseParameters showDateSelect={true} allowWebsiteSelect={!id} />
|
<BaseParameters showDateSelect={true} allowWebsiteSelect={!id} />
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
|
<FormSubmitButton variant="primary" isDisabled={queryDisabled} isLoading={isRunning}>
|
||||||
{formatMessage(labels.runQuery)}
|
{formatMessage(labels.runQuery)}
|
||||||
</SubmitButton>
|
</FormSubmitButton>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose:
|
||||||
<TextField autoComplete="off" />
|
<TextField autoComplete="off" />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<FormSubmitButton variant="primary" disabled={isPending}>
|
<FormSubmitButton variant="primary" isDisabled={isPending}>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
</FormSubmitButton>
|
</FormSubmitButton>
|
||||||
<Button isDisabled={isPending} onPress={onClose}>
|
<Button isDisabled={isPending} onPress={onClose}>
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export function WebsiteAddForm({
|
||||||
{formatMessage(labels.cancel)}
|
{formatMessage(labels.cancel)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<FormSubmitButton data-test="button-submit" disabled={false}>
|
<FormSubmitButton data-test="button-submit" isDisabled={false}>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
</FormSubmitButton>
|
</FormSubmitButton>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function TeamMemberEditForm({
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<FormSubmitButton variant="primary" disabled={false}>
|
<FormSubmitButton variant="primary" isDisabled={false}>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
</FormSubmitButton>
|
</FormSubmitButton>
|
||||||
<Button isDisabled={isPending} onPress={onClose}>
|
<Button isDisabled={isPending} onPress={onClose}>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
useToast,
|
useToast,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { getRandomChars } from '@/lib/crypto';
|
import { getRandomChars } from '@/lib/crypto';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||||
|
|
||||||
|
|
@ -21,7 +21,6 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
|
||||||
const { mutate, error } = useMutation({
|
const { mutate, error } = useMutation({
|
||||||
mutationFn: (data: any) => post(`/teams/${teamId}`, data),
|
mutationFn: (data: any) => post(`/teams/${teamId}`, data),
|
||||||
});
|
});
|
||||||
const [accessCode, setAccessCode] = useState(team.accessCode);
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { touch } = useModified();
|
const { touch } = useModified();
|
||||||
const cloudMode = !!process.env.cloudMode;
|
const cloudMode = !!process.env.cloudMode;
|
||||||
|
|
@ -35,36 +34,42 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegenerate = () => {
|
|
||||||
setAccessCode(generateId());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit} error={error} values={{ ...team, accessCode }}>
|
<Form onSubmit={handleSubmit} error={error} defaultValues={{ ...team }}>
|
||||||
<FormField name="id" label={formatMessage(labels.teamId)}>
|
{({ setValue }) => {
|
||||||
<TextField isReadOnly allowCopy />
|
return (
|
||||||
</FormField>
|
<>
|
||||||
<FormField
|
<FormField name="id" label={formatMessage(labels.teamId)}>
|
||||||
name="name"
|
<TextField isReadOnly allowCopy />
|
||||||
label={formatMessage(labels.name)}
|
</FormField>
|
||||||
rules={{ required: formatMessage(labels.required) }}
|
<FormField
|
||||||
>
|
name="name"
|
||||||
{allowEdit && <TextField />}
|
label={formatMessage(labels.name)}
|
||||||
{!allowEdit && team.name}
|
rules={{ required: formatMessage(labels.required) }}
|
||||||
</FormField>
|
>
|
||||||
{!cloudMode && allowEdit && (
|
{allowEdit && <TextField />}
|
||||||
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
|
{!allowEdit && team.name}
|
||||||
<TextField isReadOnly allowCopy />
|
</FormField>
|
||||||
</FormField>
|
{!cloudMode && allowEdit && (
|
||||||
)}
|
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
|
||||||
{allowEdit && (
|
<TextField isReadOnly allowCopy />
|
||||||
<FormButtons justifyContent="space-between">
|
</FormField>
|
||||||
{allowEdit && (
|
)}
|
||||||
<Button onPress={handleRegenerate}>{formatMessage(labels.regenerate)}</Button>
|
{allowEdit && (
|
||||||
)}
|
<FormButtons justifyContent="space-between">
|
||||||
<FormSubmitButton variant="primary">{formatMessage(labels.save)}</FormSubmitButton>
|
{allowEdit && (
|
||||||
</FormButtons>
|
<Button
|
||||||
)}
|
onPress={() => setValue('accessCode', generateId(), { shouldDirty: true })}
|
||||||
|
>
|
||||||
|
{formatMessage(labels.regenerate)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<FormSubmitButton variant="primary">{formatMessage(labels.save)}</FormSubmitButton>
|
||||||
|
</FormButtons>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export function LoginForm() {
|
||||||
<FormSubmitButton
|
<FormSubmitButton
|
||||||
data-test="button-submit"
|
data-test="button-submit"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
disabled={isPending}
|
isDisabled={isPending}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
>
|
>
|
||||||
{formatMessage(labels.login)}
|
{formatMessage(labels.login)}
|
||||||
|
|
|
||||||
7
src/components/boards/Board.tsx
Normal file
7
src/components/boards/Board.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Column } from '@umami/react-zen';
|
||||||
|
|
||||||
|
export interface BoardProps {}
|
||||||
|
|
||||||
|
export function Board(props: BoardProps) {
|
||||||
|
return <Column>{}</Column>;
|
||||||
|
}
|
||||||
|
|
@ -92,6 +92,7 @@ export function BarChart(props: BarChartProps) {
|
||||||
chartOptions={options}
|
chartOptions={options}
|
||||||
tooltip={tooltip}
|
tooltip={tooltip}
|
||||||
onTooltip={handleTooltip}
|
onTooltip={handleTooltip}
|
||||||
|
style={{ height: 400 }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
.chart {
|
|
||||||
position: relative;
|
|
||||||
height: 400px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { useState, useRef, useEffect, useMemo, ReactNode } from 'react';
|
import { useState, useRef, useEffect, useMemo, ReactNode, HTMLAttributes } from 'react';
|
||||||
import { Loading } from '@umami/react-zen';
|
import { Loading } from '@umami/react-zen';
|
||||||
import classNames from 'classnames';
|
|
||||||
import ChartJS, { LegendItem, ChartOptions } from 'chart.js/auto';
|
import ChartJS, { LegendItem, ChartOptions } from 'chart.js/auto';
|
||||||
import { Legend } from '@/components/metrics/Legend';
|
import { Legend } from '@/components/metrics/Legend';
|
||||||
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
|
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
|
||||||
import styles from './Chart.module.css';
|
|
||||||
|
|
||||||
export interface ChartProps {
|
export interface ChartProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
type?: 'bar' | 'bubble' | 'doughnut' | 'pie' | 'line' | 'polarArea' | 'radar' | 'scatter';
|
type?: 'bar' | 'bubble' | 'doughnut' | 'pie' | 'line' | 'polarArea' | 'radar' | 'scatter';
|
||||||
data?: object;
|
data?: object;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
|
@ -15,7 +13,6 @@ export interface ChartProps {
|
||||||
onCreate?: (chart: any) => void;
|
onCreate?: (chart: any) => void;
|
||||||
onUpdate?: (chart: any) => void;
|
onUpdate?: (chart: any) => void;
|
||||||
onTooltip?: (model: any) => void;
|
onTooltip?: (model: any) => void;
|
||||||
className?: string;
|
|
||||||
chartOptions?: ChartOptions;
|
chartOptions?: ChartOptions;
|
||||||
tooltip?: ReactNode;
|
tooltip?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
@ -25,13 +22,14 @@ export function Chart({
|
||||||
data,
|
data,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||||
tooltip,
|
|
||||||
updateMode,
|
updateMode,
|
||||||
onCreate,
|
onCreate,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onTooltip,
|
onTooltip,
|
||||||
className,
|
className,
|
||||||
chartOptions,
|
chartOptions,
|
||||||
|
tooltip,
|
||||||
|
...props
|
||||||
}: ChartProps) {
|
}: ChartProps) {
|
||||||
const canvas = useRef(null);
|
const canvas = useRef(null);
|
||||||
const chart = useRef(null);
|
const chart = useRef(null);
|
||||||
|
|
@ -137,7 +135,7 @@ export function Chart({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classNames(styles.chart, className)}>
|
<div {...props}>
|
||||||
{isLoading && <Loading position="page" icon="dots" />}
|
{isLoading && <Loading position="page" icon="dots" />}
|
||||||
<canvas ref={canvas} />
|
<canvas ref={canvas} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export function DataGrid({
|
||||||
searchDelay = 600,
|
searchDelay = 600,
|
||||||
allowSearch = true,
|
allowSearch = true,
|
||||||
allowPaging = true,
|
allowPaging = true,
|
||||||
autoFocus = true,
|
autoFocus,
|
||||||
renderEmpty,
|
renderEmpty,
|
||||||
children,
|
children,
|
||||||
}: DataTableProps) {
|
}: DataTableProps) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ body {
|
||||||
font-family: var(--font-family), sans-serif;
|
font-family: var(--font-family), sans-serif;
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
background-color: var(--background-color);
|
background-color: var(--base-color-1);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue