Small fixes.

This commit is contained in:
Mike Cao 2025-03-15 20:34:42 -07:00
parent 5536e0b7e7
commit efd4f4ca00
31 changed files with 621 additions and 586 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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();

View file

@ -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 }) => {

View file

@ -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>
);
} }

View file

@ -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>
); );
} }

View 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',
};

View file

@ -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 />;

View file

@ -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>
); );
} }

View file

@ -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>

View file

@ -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>
); );

View file

@ -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>
); );
} }

View file

@ -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 />

View file

@ -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>

View file

@ -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)}

View file

@ -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>
); );

View file

@ -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>
); );

View file

@ -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>

View file

@ -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>

View file

@ -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>
); );

View file

@ -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}>

View file

@ -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>

View file

@ -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}>

View file

@ -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>
); );
} }

View file

@ -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)}

View file

@ -0,0 +1,7 @@
import { Column } from '@umami/react-zen';
export interface BoardProps {}
export function Board(props: BoardProps) {
return <Column>{}</Column>;
}

View file

@ -92,6 +92,7 @@ export function BarChart(props: BarChartProps) {
chartOptions={options} chartOptions={options}
tooltip={tooltip} tooltip={tooltip}
onTooltip={handleTooltip} onTooltip={handleTooltip}
style={{ height: 400 }}
/> />
); );
} }

View file

@ -1,11 +0,0 @@
.chart {
position: relative;
height: 400px;
overflow: hidden;
}
.tooltip {
display: flex;
flex-direction: column;
gap: 10px;
}

View file

@ -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>

View file

@ -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) {

View file

@ -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;
} }