Compare commits

..

No commits in common. "1498da2d02b3b8f4bdd53a9e0d834fc690ba0f2e" and "128217c0f4d49e08035c8f7a5db806fa49c7825c" have entirely different histories.

20 changed files with 11 additions and 256 deletions

View file

@ -3,19 +3,14 @@ import { Column } from '@umami/react-zen';
import { PageHeader } from '@/components/common/PageHeader';
import { Panel } from '@/components/common/Panel';
import { useMessages } from '@/components/hooks';
import { TeamsAddButton } from '../../teams/TeamsAddButton';
import { AdminTeamsDataTable } from './AdminTeamsDataTable';
export function AdminTeamsPage() {
const { formatMessage, labels } = useMessages();
const handleSave = () => {};
return (
<Column gap="6" margin="2">
<PageHeader title={formatMessage(labels.teams)}>
<TeamsAddButton onSave={handleSave} isAdmin={true} />
</PageHeader>
<PageHeader title={formatMessage(labels.teams)} />
<Panel>
<AdminTeamsDataTable />
</Panel>

View file

@ -7,17 +7,8 @@ import {
TextField,
} from '@umami/react-zen';
import { useMessages, useUpdateQuery } from '@/components/hooks';
import { UserSelect } from '@/components/input/UserSelect';
export function TeamAddForm({
onSave,
onClose,
isAdmin,
}: {
onSave: () => void;
onClose: () => void;
isAdmin: boolean;
}) {
export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
const { formatMessage, labels, getErrorMessage } = useMessages();
const { mutateAsync, error, isPending } = useUpdateQuery('/teams');
@ -35,11 +26,6 @@ export function TeamAddForm({
<FormField name="name" label={formatMessage(labels.name)}>
<TextField autoComplete="off" />
</FormField>
{isAdmin && (
<FormField name="ownerId" label={formatMessage(labels.teamOwner)}>
<UserSelect buttonProps={{ style: { outline: 'none' } }} />
</FormField>
)}
<FormButtons>
<Button isDisabled={isPending} onPress={onClose}>
{formatMessage(labels.cancel)}

View file

@ -1,76 +0,0 @@
import {
Button,
Form,
FormButtons,
FormField,
FormSubmitButton,
ListItem,
Select,
} from '@umami/react-zen';
import { useMessages, useUpdateQuery } from '@/components/hooks';
import { UserSelect } from '@/components/input/UserSelect';
import { ROLES } from '@/lib/constants';
const roles = [ROLES.teamManager, ROLES.teamMember, ROLES.teamViewOnly];
export function TeamMemberAddForm({
teamId,
onSave,
onClose,
}: {
teamId: string;
onSave?: () => void;
onClose?: () => void;
}) {
const { formatMessage, labels, getErrorMessage } = useMessages();
const { mutateAsync, error, isPending } = useUpdateQuery(`/teams/${teamId}/users`);
const handleSubmit = async (data: any) => {
await mutateAsync(data, {
onSuccess: async () => {
onSave?.();
onClose?.();
},
});
};
const renderRole = role => {
switch (role) {
case ROLES.teamManager:
return formatMessage(labels.manager);
case ROLES.teamMember:
return formatMessage(labels.member);
case ROLES.teamViewOnly:
return formatMessage(labels.viewOnly);
}
};
return (
<Form onSubmit={handleSubmit} error={getErrorMessage(error)}>
<FormField
name="userId"
label={formatMessage(labels.username)}
rules={{ required: 'Required' }}
>
<UserSelect teamId={teamId} />
</FormField>
<FormField name="role" label={formatMessage(labels.role)} rules={{ required: 'Required' }}>
<Select items={roles} renderValue={value => renderRole(value as any)}>
{roles.map(value => (
<ListItem key={value} id={value}>
{renderRole(value)}
</ListItem>
))}
</Select>
</FormField>
<FormButtons>
<Button isDisabled={isPending} onPress={onClose}>
{formatMessage(labels.cancel)}
</Button>
<FormSubmitButton variant="primary" isDisabled={isPending}>
{formatMessage(labels.save)}
</FormSubmitButton>
</FormButtons>
</Form>
);
}

View file

@ -4,13 +4,7 @@ import { Plus } from '@/components/icons';
import { messages } from '@/components/messages';
import { TeamAddForm } from './TeamAddForm';
export function TeamsAddButton({
onSave,
isAdmin = false,
}: {
onSave?: () => void;
isAdmin?: boolean;
}) {
export function TeamsAddButton({ onSave }: { onSave?: () => void }) {
const { formatMessage, labels } = useMessages();
const { toast } = useToast();
const { touch } = useModified();
@ -31,7 +25,7 @@ export function TeamsAddButton({
</Button>
<Modal>
<Dialog title={formatMessage(labels.createTeam)} style={{ width: 400 }}>
{({ close }) => <TeamAddForm onSave={handleSave} onClose={close} isAdmin={isAdmin} />}
{({ close }) => <TeamAddForm onSave={handleSave} onClose={close} />}
</Dialog>
</Modal>
</DialogTrigger>

View file

@ -1,40 +0,0 @@
import { Button, Dialog, DialogTrigger, Icon, Modal, Text, useToast } from '@umami/react-zen';
import { useMessages, useModified } from '@/components/hooks';
import { Plus } from '@/components/icons';
import { messages } from '@/components/messages';
import { TeamMemberAddForm } from './TeamMemberAddForm';
export function TeamsMemberAddButton({
teamId,
onSave,
}: {
teamId: string;
onSave?: () => void;
isAdmin?: boolean;
}) {
const { formatMessage, labels } = useMessages();
const { toast } = useToast();
const { touch } = useModified();
const handleSave = async () => {
toast(formatMessage(messages.saved));
touch('teams:members');
onSave?.();
};
return (
<DialogTrigger>
<Button>
<Icon>
<Plus />
</Icon>
<Text>{formatMessage(labels.addMember)}</Text>
</Button>
<Modal>
<Dialog title={formatMessage(labels.addMember)} style={{ width: 400 }}>
{({ close }) => <TeamMemberAddForm teamId={teamId} onSave={handleSave} onClose={close} />}
</Dialog>
</Modal>
</DialogTrigger>
);
}

View file

@ -1,12 +1,10 @@
import { Column, Heading, Row } from '@umami/react-zen';
import { Column } from '@umami/react-zen';
import { TeamLeaveButton } from '@/app/(main)/teams/TeamLeaveButton';
import { PageHeader } from '@/components/common/PageHeader';
import { Panel } from '@/components/common/Panel';
import { useLoginQuery, useMessages, useNavigation, useTeam } from '@/components/hooks';
import { useLoginQuery, useNavigation, useTeam } from '@/components/hooks';
import { Users } from '@/components/icons';
import { labels } from '@/components/messages';
import { ROLES } from '@/lib/constants';
import { TeamsMemberAddButton } from '../TeamsMemberAddButton';
import { TeamEditForm } from './TeamEditForm';
import { TeamManage } from './TeamManage';
import { TeamMembersDataTable } from './TeamMembersDataTable';
@ -15,7 +13,6 @@ export function TeamSettings({ teamId }: { teamId: string }) {
const team: any = useTeam();
const { user } = useLoginQuery();
const { pathname } = useNavigation();
const { formatMessage } = useMessages();
const isAdmin = pathname.includes('/admin');
@ -40,10 +37,6 @@ export function TeamSettings({ teamId }: { teamId: string }) {
<TeamEditForm teamId={teamId} allowEdit={canEdit} showAccessCode={canEdit} />
</Panel>
<Panel>
<Row alignItems="center" justifyContent="space-between">
<Heading size="2">{formatMessage(labels.members)}</Heading>
{isAdmin && <TeamsMemberAddButton teamId={teamId} />}
</Row>
<TeamMembersDataTable teamId={teamId} allowEdit={canEdit} />
</Panel>
{isTeamOwner && (

View file

@ -1,10 +1,10 @@
'use client';
import { Column, Grid, ListItem, Row, SearchField, Select } from '@umami/react-zen';
import { FilterButtons } from 'dist';
import { useState } from 'react';
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
import { Panel } from '@/components/common/Panel';
import { useDateRange, useMessages } from '@/components/hooks';
import { FilterButtons } from '@/components/input/FilterButtons';
import { Journey } from './Journey';
const JOURNEY_STEPS = [2, 3, 4, 5, 6, 7];

View file

@ -169,12 +169,6 @@ export function WebsiteExpandedMenu({
path: updateParams({ view: 'hostname' }),
icon: <Network />,
},
{
id: 'distinctId',
label: formatMessage(labels.distinctId),
path: updateParams({ view: 'distinctId' }),
icon: <Tag />,
},
{
id: 'tag',
label: formatMessage(labels.tag),

View file

@ -93,11 +93,6 @@ export function CompareTables({ websiteId }: { websiteId: string }) {
label: formatMessage(labels.hostname),
path: renderPath('hostname'),
},
{
id: 'distinctId',
label: formatMessage(labels.distinctId),
path: renderPath('distinctId'),
},
{
id: 'tag',
label: formatMessage(labels.tags),

View file

@ -28,7 +28,6 @@ export async function GET(request: Request) {
export async function POST(request: Request) {
const schema = z.object({
name: z.string().max(50),
ownerId: z.uuid().optional(),
});
const { auth, body, error } = await parseRequest(request, schema);
@ -41,7 +40,7 @@ export async function POST(request: Request) {
return unauthorized();
}
const { name, ownerId } = body;
const { name } = body;
const team = await createTeam(
{
@ -49,7 +48,7 @@ export async function POST(request: Request) {
name,
accessCode: `team_${getRandomChars(16)}`,
},
ownerId ?? auth.user.id,
auth.user.id,
);
return json(team);

View file

@ -15,7 +15,6 @@ export function useFields() {
{ name: 'region', type: 'string', label: formatMessage(labels.region) },
{ name: 'city', type: 'string', label: formatMessage(labels.city) },
{ name: 'hostname', type: 'string', label: formatMessage(labels.hostname) },
{ name: 'distinctId', type: 'string', label: formatMessage(labels.distinctId) },
{ name: 'tag', type: 'string', label: formatMessage(labels.tag) },
{ name: 'event', type: 'string', label: formatMessage(labels.event) },
];

View file

@ -18,7 +18,6 @@ export function useFilterParameters() {
event,
tag,
hostname,
distinctId,
page,
pageSize,
search,
@ -43,7 +42,6 @@ export function useFilterParameters() {
event,
tag,
hostname,
distinctId,
search,
segment,
cohort,
@ -63,7 +61,6 @@ export function useFilterParameters() {
event,
tag,
hostname,
distinctId,
page,
pageSize,
search,

View file

@ -61,9 +61,7 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP
websiteId={websiteId}
value={currentFilters}
onChange={setCurrentFilters}
exclude={
excludeFilters ? ['path', 'title', 'hostname', 'distinctId', 'tag', 'event'] : []
}
exclude={excludeFilters ? ['path', 'title', 'hostname', 'tag', 'event'] : []}
/>
</TabPanel>
<TabPanel id="segments">

View file

@ -1,71 +0,0 @@
import { ListItem, Row, Select, type SelectProps, Text } from '@umami/react-zen';
import { useMemo, useState } from 'react';
import { Empty } from '@/components/common/Empty';
import { useMessages, useTeamMembersQuery, useUsersQuery } from '@/components/hooks';
export function UserSelect({
teamId,
onChange,
...props
}: {
teamId?: string;
} & SelectProps) {
const { formatMessage, messages } = useMessages();
const { data: users, isLoading: usersLoading } = useUsersQuery();
const { data: teamMembers, isLoading: teamMembersLoading } = useTeamMembersQuery(teamId);
const [username, setUsername] = useState<string>();
const [search, setSearch] = useState('');
const listItems = useMemo(() => {
if (!users) {
return [];
}
if (!teamId || !teamMembers) {
return users.data;
}
const teamMemberIds = teamMembers.data.map(({ userId }) => userId);
return users.data.filter(({ id }) => !teamMemberIds.includes(id));
}, [users, teamMembers, teamId]);
const handleSearch = (value: string) => {
setSearch(value);
};
const handleOpenChange = () => {
setSearch('');
};
const handleChange = (id: string) => {
setUsername(listItems.find(item => item.id === id)?.username);
onChange(id);
};
const renderValue = () => {
return (
<Row maxWidth="160px">
<Text truncate>{username}</Text>
</Row>
);
};
return (
<Select
{...props}
items={listItems}
value={username}
isLoading={usersLoading || (teamId && teamMembersLoading)}
allowSearch={true}
searchValue={search}
onSearch={handleSearch}
onChange={handleChange}
onOpenChange={handleOpenChange}
renderValue={renderValue}
listProps={{
renderEmptyState: () => <Empty message={formatMessage(messages.noResultsFound)} />,
style: { maxHeight: 'calc(42vh - 65px)' },
}}
>
{({ id, username }: any) => <ListItem key={id}>{username}</ListItem>}
</Select>
);
}

View file

@ -19,13 +19,11 @@ export * from '@/app/(main)/teams/TeamAddForm';
export * from '@/app/(main)/teams/TeamJoinForm';
export * from '@/app/(main)/teams/TeamLeaveButton';
export * from '@/app/(main)/teams/TeamLeaveForm';
export * from '@/app/(main)/teams/TeamMemberAddForm';
export * from '@/app/(main)/teams/TeamProvider';
export * from '@/app/(main)/teams/TeamsAddButton';
export * from '@/app/(main)/teams/TeamsDataTable';
export * from '@/app/(main)/teams/TeamsHeader';
export * from '@/app/(main)/teams/TeamsJoinButton';
export * from '@/app/(main)/teams/TeamsMemberAddButton';
export * from '@/app/(main)/teams/TeamsTable';
export * from '@/app/(main)/websites/[websiteId]/settings/WebsiteData';
export * from '@/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm';

View file

@ -55,7 +55,6 @@ export const SESSION_COLUMNS = [
'country',
'city',
'region',
'distinctId',
];
export const SEGMENT_TYPES = {
@ -70,7 +69,6 @@ export const FILTER_COLUMNS = {
referrer: 'referrer_domain',
domain: 'referrer_domain',
hostname: 'hostname',
distinctId: 'distinct_id',
title: 'page_title',
query: 'url_query',
os: 'os',

View file

@ -36,7 +36,6 @@ export const filterParams = {
city: z.string().optional(),
tag: z.string().optional(),
hostname: z.string().optional(),
distinctId: z.string().optional(),
language: z.string().optional(),
event: z.string().optional(),
segment: z.uuid().optional(),
@ -90,7 +89,6 @@ export const fieldsParam = z.enum([
'city',
'tag',
'hostname',
'distinctId',
'language',
'event',
]);

View file

@ -72,7 +72,6 @@ async function relationalQuery(
${filterQuery}
group by name, website_event.session_id, website_event.visit_id
) as t
where name != ''
group by name
order by visitors desc, visits desc
limit ${limit}

View file

@ -131,5 +131,5 @@ function parseFields(fields: string[]) {
}
function parseFieldsByName(fields: string[]) {
return `${fields.map(name => `"${name}"`).join(',')}`;
return `${fields.map(name => name).join(',')}`;
}

View file

@ -82,7 +82,6 @@ async function relationalQuery(
group by name, website_event.session_id, website_event.visit_id
${includeCountry ? ', country' : ''}
) as t
where name != ''
group by name
${includeCountry ? ', country' : ''}
order by visitors desc, visits desc