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

@ -3,10 +3,10 @@ import { Grid, Loading } from '@umami/react-zen';
import Script from 'next/script';
import { usePathname } from 'next/navigation';
import { UpdateNotice } from './UpdateNotice';
import { Nav } from '@/app/(main)/Nav';
import { NavBar } from '@/app/(main)/NavBar';
import { Page } from '@/components/layout/Page';
import { useLogin, useConfig } from '@/components/hooks';
import { Nav } from '@/app/(main)/Nav';
export function App({ children }) {
const { user, isLoading, error } = useLogin();

View file

@ -46,9 +46,9 @@ export function Nav() {
].filter(n => n);
return (
<SideNav isCollapsed={isCollapsed}>
<SideNav isCollapsed={isCollapsed} variant="3">
<SideNavSection>
<SideNavHeader name="umami" icon={<Icons.Logo />} />
<SideNavHeader label="umami" icon={<Icons.Logo />} />
</SideNavSection>
<SideNavSection>
{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() {
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';
export function Board() {
export function Board({ boardId }: { boardId: string }) {
return (
<Column>
<Heading>Board title</Heading>
<div>{boardId}</div>
</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 { BoardsPage } from './BoardsPage';
export default function () {
return <BoardsPage />;

View file

@ -22,7 +22,7 @@ export function DateRangeSetting() {
endDate={dateRange.endDate}
onChange={handleChange}
/>
<Button onClick={handleReset}>{formatMessage(labels.reset)}</Button>
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
</Flexbox>
);
}

View file

@ -20,17 +20,16 @@ export function LanguageSetting() {
const handleReset = () => saveLocale(DEFAULT_LOCALE);
console.log({ options });
return (
<Flexbox gap={10}>
<Select
items={options}
value={locale}
onChange={val => saveLocale(val as string)}
allowSearch={true}
onSearch={setSearch}
menuProps={{ className: styles.menu }}
>
{item => <ListItem key={item}>{languages[item].label}</ListItem>}
<Select value={locale} onChange={val => saveLocale(val as string)}>
{options.map(item => (
<ListItem key={item} id={item}>
{languages[item].label}
</ListItem>
))}
</Select>
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
</Flexbox>

View file

@ -1,5 +1,4 @@
import { useRef } from 'react';
import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from '@umami/react-zen';
import { Form, FormField, FormButtons, PasswordField, Button } from '@umami/react-zen';
import { useApi, useMessages } from '@/components/hooks';
export function PasswordEditForm({ onSave, onClose }) {
@ -8,7 +7,6 @@ export function PasswordEditForm({ onSave, onClose }) {
const { mutate, error, isPending } = useMutation({
mutationFn: (data: any) => post('/me/password', data),
});
const ref = useRef(null);
const handleSubmit = async (data: any) => {
mutate(data, {
@ -19,48 +17,48 @@ export function PasswordEditForm({ onSave, onClose }) {
});
};
const samePassword = (value: string) => {
if (value !== ref?.current?.getValues('newPassword')) {
const samePassword = (value: string, values: { [key: string]: any }) => {
if (value !== values.newPassword) {
return formatMessage(messages.noMatchPassword);
}
return true;
};
return (
<Form ref={ref} onSubmit={handleSubmit} error={error}>
<FormRow label={formatMessage(labels.currentPassword)}>
<FormInput name="currentPassword" rules={{ required: 'Required' }}>
<PasswordField autoComplete="current-password" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.newPassword)}>
<FormInput
name="newPassword"
rules={{
required: 'Required',
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
}}
>
<PasswordField autoComplete="new-password" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.confirmPassword)}>
<FormInput
name="confirmPassword"
rules={{
required: formatMessage(labels.required),
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
validate: samePassword,
}}
>
<PasswordField autoComplete="confirm-password" />
</FormInput>
</FormRow>
<FormButtons flex>
<Button type="submit" variant="primary" disabled={isPending}>
<Form onSubmit={handleSubmit} error={error}>
<FormField
label={formatMessage(labels.currentPassword)}
name="currentPassword"
rules={{ required: 'Required' }}
>
<PasswordField autoComplete="current-password" />
</FormField>
<FormField
name="newPassword"
label={formatMessage(labels.newPassword)}
rules={{
required: 'Required',
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
}}
>
<PasswordField autoComplete="new-password" />
</FormField>
<FormField
name="confirmPassword"
label={formatMessage(labels.confirmPassword)}
rules={{
required: formatMessage(labels.required),
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
validate: samePassword,
}}
>
<PasswordField autoComplete="confirm-password" />
</FormField>
<FormButtons>
<Button type="submit" variant="primary" isDisabled={isPending}>
{formatMessage(labels.save)}
</Button>
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
</FormButtons>
</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 { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
import { LanguageSetting } from '@/app/(main)/profile/LanguageSetting';
@ -33,26 +33,43 @@ export function ProfileSettings() {
};
return (
<Form>
<FormRow label={formatMessage(labels.username)}>{username}</FormRow>
<FormRow label={formatMessage(labels.role)}>{renderRole(role)}</FormRow>
<Column gap="6">
<Column>
<Label>{formatMessage(labels.username)}</Label>
{username}
</Column>
<Column>
<Label>{formatMessage(labels.role)}</Label>
{renderRole(role)}
</Column>
{!cloudMode && (
<FormRow label={formatMessage(labels.password)}>
<Column>
<Label>{formatMessage(labels.password)}</Label>
<PasswordChangeButton />
</FormRow>
</Column>
)}
<FormRow label={formatMessage(labels.defaultDateRange)}>
<Column>
<Label>{formatMessage(labels.defaultDateRange)}</Label>
<DateRangeSetting />
</FormRow>
<FormRow label={formatMessage(labels.language)}>
</Column>
<Column>
<Label>{formatMessage(labels.language)}</Label>
<LanguageSetting />
</FormRow>
<FormRow label={formatMessage(labels.timezone)}>
</Column>
<Column>
<Label>{formatMessage(labels.timezone)}</Label>
<TimezoneSetting />
</FormRow>
<FormRow label={formatMessage(labels.theme)}>
</Column>
<Column>
<Label>{formatMessage(labels.theme)}</Label>
<ThemeSetting />
</FormRow>
</Form>
</Column>
</Column>
);
}

View file

@ -11,7 +11,7 @@ export function ThemeSetting() {
<div className={styles.buttons}>
<Button
className={classNames({ [styles.active]: theme === 'light' })}
onClick={() => saveTheme('light')}
onPress={() => saveTheme('light')}
>
<Icon>
<Icons.Sun />
@ -19,7 +19,7 @@ export function ThemeSetting() {
</Button>
<Button
className={classNames({ [styles.active]: theme === 'dark' })}
onClick={() => saveTheme('dark')}
onPress={() => saveTheme('dark')}
>
<Icon>
<Icons.Moon />

View file

@ -10,7 +10,7 @@ export function TimezoneSetting() {
const [search, setSearch] = useState('');
const { formatMessage, labels } = useMessages();
const { timezone, saveTimezone } = useTimezone();
const options = search
const items = search
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
: timezones;
@ -20,13 +20,13 @@ export function TimezoneSetting() {
<Row gap="3">
<Select
className={styles.dropdown}
items={options}
items={items}
value={timezone}
onChange={(value: any) => saveTimezone(value)}
allowSearch={true}
onSearch={setSearch}
>
{item => (
{(item: any) => (
<ListItem key={item} id={item}>
{item}
</ListItem>

View file

@ -93,7 +93,7 @@ export function ReportHeader({ icon }) {
<LoadingButton
variant="primary"
isLoading={isCreating || isUpdating}
disabled={!websiteId || !dateRange?.value || !name}
isDisabled={!websiteId || !dateRange?.value || !name}
onPress={handleSave}
>
{formatMessage(labels.save)}

View file

@ -1,12 +1,12 @@
import { useContext } from 'react';
import {
Form,
FormRow,
FormField,
FormButtons,
SubmitButton,
PopupTrigger,
FormSubmitButton,
DialogTrigger,
Icon,
Popup,
Popover,
} from '@umami/react-zen';
import { Empty } from '@/components/common/Empty';
import { Icons } from '@/components/icons';
@ -75,12 +75,12 @@ export function EventDataParameters() {
const AddButton = ({ group, onAdd }) => {
return (
<PopupTrigger>
<DialogTrigger>
<Icon>
<Icons.Plus />
</Icon>
<Popup position="bottom" alignment="start">
{(close: () => void) => {
<Popover placement="bottom start">
{({ close }: any) => {
return (
<FieldAddForm
fields={data.map(({ dataKey, eventDataType }) => ({
@ -93,8 +93,8 @@ export function EventDataParameters() {
/>
);
}}
</Popup>
</PopupTrigger>
</Popover>
</DialogTrigger>
);
};
@ -106,11 +106,7 @@ export function EventDataParameters() {
hasData &&
parameterGroups.map(({ label, group }) => {
return (
<FormRow
key={label}
label={label}
action={<AddButton group={group} onAdd={handleAdd} />}
>
<FormField name={label} key={label} label={label}>
<ParameterList>
{parameterData[group].map(({ name, value }) => {
return (
@ -134,13 +130,14 @@ export function EventDataParameters() {
);
})}
</ParameterList>
</FormRow>
<AddButton group={group} onAdd={handleAdd} />
</FormField>
);
})}
<FormButtons>
<SubmitButton variant="primary" disabled={!queryEnabled} isLoading={isRunning}>
<FormSubmitButton variant="primary" isDisabled={!queryEnabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormSubmitButton>
</FormButtons>
</Form>
);

View file

@ -4,11 +4,10 @@ import {
Icon,
Form,
FormButtons,
FormInput,
FormRow,
PopupTrigger,
Popup,
SubmitButton,
FormField,
DialogTrigger,
Popover,
FormSubmitButton,
TextField,
Button,
} from '@umami/react-zen';
@ -60,37 +59,36 @@ export function FunnelParameters() {
const AddStepButton = () => {
return (
<PopupTrigger>
<DialogTrigger>
<Button>
<Icon>
<Icons.Plus />
</Icon>
</Button>
<Popup alignment="start">
<Popover placement="start">
<PopupForm>
<FunnelStepAddForm onChange={handleAddStep} />
</PopupForm>
</Popup>
</PopupTrigger>
</Popover>
</DialogTrigger>
);
};
return (
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
<BaseParameters allowWebsiteSelect={!id} />
<FormRow label={formatMessage(labels.window)}>
<FormInput
name="window"
rules={{ required: formatMessage(labels.required), pattern: /[0-9]+/ }}
>
<TextField autoComplete="off" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.steps)} action={<AddStepButton />}>
<FormField
label={formatMessage(labels.window)}
name="window"
rules={{ required: formatMessage(labels.required), pattern: /[0-9]+/ }}
>
<TextField autoComplete="off" />
</FormField>
<FormField name="steps" label={formatMessage(labels.steps)}>
<ParameterList>
{steps.map((step: { type: string; value: string }, index: number) => {
return (
<PopupTrigger key={index}>
<DialogTrigger key={index}>
<ParameterList.Item
className={styles.item}
icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
@ -100,8 +98,8 @@ export function FunnelParameters() {
<div>{step.value}</div>
</div>
</ParameterList.Item>
<Popup alignment="start">
{(close: () => void) => (
<Popover placement="start">
{({ close }: any) => (
<PopupForm>
<FunnelStepAddForm
type={step.type}
@ -110,16 +108,17 @@ export function FunnelParameters() {
/>
</PopupForm>
)}
</Popup>
</PopupTrigger>
</Popover>
</DialogTrigger>
);
})}
</ParameterList>
</FormRow>
<AddStepButton />
</FormField>
<FormButtons>
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
<FormSubmitButton variant="primary" isDisabled={queryDisabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormSubmitButton>
</FormButtons>
</Form>
);

View file

@ -194,7 +194,7 @@ export function JourneyView() {
<div className={styles.name} title={name}>
<TextOverflow> {name}</TextOverflow>
</div>
<TooltipPopup label={dropOffPercent} disabled={!selected}>
<TooltipPopup label={dropOffPercent} isDisabled={!selected}>
<div className={styles.count} title={nodeCount}>
{formatLongNumber(nodeCount)}
</div>

View file

@ -27,7 +27,7 @@ export function RetentionParameters() {
<BaseParameters showDateSelect={false} allowWebsiteSelect={!id} />
<FormButtons>
<FormSubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
<FormSubmitButton variant="primary" isDisabled={queryDisabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</FormSubmitButton>
</FormButtons>

View file

@ -1,6 +1,6 @@
import { useContext } from 'react';
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 { BaseParameters } from '../[reportId]/BaseParameters';
@ -25,9 +25,9 @@ export function UTMParameters() {
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
<BaseParameters showDateSelect={true} allowWebsiteSelect={!id} />
<FormButtons>
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
<FormSubmitButton variant="primary" isDisabled={queryDisabled} isLoading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormSubmitButton>
</FormButtons>
</Form>
);

View file

@ -30,7 +30,7 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose:
<TextField autoComplete="off" />
</FormField>
<FormButtons>
<FormSubmitButton variant="primary" disabled={isPending}>
<FormSubmitButton variant="primary" isDisabled={isPending}>
{formatMessage(labels.save)}
</FormSubmitButton>
<Button isDisabled={isPending} onPress={onClose}>

View file

@ -55,7 +55,7 @@ export function WebsiteAddForm({
{formatMessage(labels.cancel)}
</Button>
)}
<FormSubmitButton data-test="button-submit" disabled={false}>
<FormSubmitButton data-test="button-submit" isDisabled={false}>
{formatMessage(labels.save)}
</FormSubmitButton>
</Row>

View file

@ -53,7 +53,7 @@ export function TeamMemberEditForm({
</FormField>
<FormButtons>
<FormSubmitButton variant="primary" disabled={false}>
<FormSubmitButton variant="primary" isDisabled={false}>
{formatMessage(labels.save)}
</FormSubmitButton>
<Button isDisabled={isPending} onPress={onClose}>

View file

@ -8,7 +8,7 @@ import {
useToast,
} from '@umami/react-zen';
import { getRandomChars } from '@/lib/crypto';
import { useContext, useState } from 'react';
import { useContext } from 'react';
import { useApi, useMessages, useModified } from '@/components/hooks';
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
@ -21,7 +21,6 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
const { mutate, error } = useMutation({
mutationFn: (data: any) => post(`/teams/${teamId}`, data),
});
const [accessCode, setAccessCode] = useState(team.accessCode);
const { toast } = useToast();
const { touch } = useModified();
const cloudMode = !!process.env.cloudMode;
@ -35,36 +34,42 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
});
};
const handleRegenerate = () => {
setAccessCode(generateId());
};
return (
<Form onSubmit={handleSubmit} error={error} values={{ ...team, accessCode }}>
<FormField name="id" label={formatMessage(labels.teamId)}>
<TextField isReadOnly allowCopy />
</FormField>
<FormField
name="name"
label={formatMessage(labels.name)}
rules={{ required: formatMessage(labels.required) }}
>
{allowEdit && <TextField />}
{!allowEdit && team.name}
</FormField>
{!cloudMode && allowEdit && (
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
<TextField isReadOnly allowCopy />
</FormField>
)}
{allowEdit && (
<FormButtons justifyContent="space-between">
{allowEdit && (
<Button onPress={handleRegenerate}>{formatMessage(labels.regenerate)}</Button>
)}
<FormSubmitButton variant="primary">{formatMessage(labels.save)}</FormSubmitButton>
</FormButtons>
)}
<Form onSubmit={handleSubmit} error={error} defaultValues={{ ...team }}>
{({ setValue }) => {
return (
<>
<FormField name="id" label={formatMessage(labels.teamId)}>
<TextField isReadOnly allowCopy />
</FormField>
<FormField
name="name"
label={formatMessage(labels.name)}
rules={{ required: formatMessage(labels.required) }}
>
{allowEdit && <TextField />}
{!allowEdit && team.name}
</FormField>
{!cloudMode && allowEdit && (
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
<TextField isReadOnly allowCopy />
</FormField>
)}
{allowEdit && (
<FormButtons justifyContent="space-between">
{allowEdit && (
<Button
onPress={() => setValue('accessCode', generateId(), { shouldDirty: true })}
>
{formatMessage(labels.regenerate)}
</Button>
)}
<FormSubmitButton variant="primary">{formatMessage(labels.save)}</FormSubmitButton>
</FormButtons>
)}
</>
);
}}
</Form>
);
}

View file

@ -61,7 +61,7 @@ export function LoginForm() {
<FormSubmitButton
data-test="button-submit"
variant="primary"
disabled={isPending}
isDisabled={isPending}
style={{ flex: 1 }}
>
{formatMessage(labels.login)}