mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Admin section updates.
This commit is contained in:
parent
87449ece9e
commit
1b81074752
20 changed files with 274 additions and 647 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -19,22 +19,22 @@ export function SideNav(props: any) {
|
|||
const links = [
|
||||
{
|
||||
label: formatMessage(labels.websites),
|
||||
href: '/websites',
|
||||
href: renderUrl('/websites', false),
|
||||
icon: <Globe />,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.boards),
|
||||
href: '/boards',
|
||||
href: renderUrl('/boards', false),
|
||||
icon: <LayoutDashboard />,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.links),
|
||||
href: '/links',
|
||||
href: renderUrl('/links', false),
|
||||
icon: <LinkIcon />,
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.pixels),
|
||||
href: '/pixels',
|
||||
href: renderUrl('/pixels', false),
|
||||
icon: <Grid2X2 />,
|
||||
},
|
||||
{
|
||||
|
|
@ -57,7 +57,7 @@ export function SideNav(props: any) {
|
|||
<SidebarSection>
|
||||
{links.map(({ href, label, icon }) => {
|
||||
return (
|
||||
<Link key={href} href={renderUrl(href, false)} role="button">
|
||||
<Link key={href} href={href} role="button">
|
||||
<SidebarItem label={label} icon={icon} isSelected={pathname.startsWith(href)} />
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
'use client';
|
||||
import { TeamDetails } from '@/app/(main)/teams/[teamId]/settings/team/TeamDetails';
|
||||
import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||
|
||||
export function AdminTeamPage({ teamId }: { teamId: string }) {
|
||||
return (
|
||||
<>
|
||||
<TeamProvider teamId={teamId}>
|
||||
<TeamDetails teamId={teamId} />
|
||||
</>
|
||||
</TeamProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
import { AdminTeamPage } from './AdminTeamPage';
|
||||
import { TeamProvider } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default async function ({ params }: { params: Promise<{ teamId: string }> }) {
|
||||
const { teamId } = await params;
|
||||
|
||||
return (
|
||||
<TeamProvider teamId={teamId}>
|
||||
<AdminTeamPage teamId={teamId} />
|
||||
</TeamProvider>
|
||||
);
|
||||
return <AdminTeamPage teamId={teamId} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ export default async function ({ params }: { params: Promise<{ userId: string }>
|
|||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Users',
|
||||
title: 'User',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
'use client';
|
||||
import { WebsiteSettings } from '@/app/(main)/settings/websites/[websiteId]/WebsiteSettings';
|
||||
import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||
|
||||
export function AdminWebsitePage({ websiteId }: { websiteId: string }) {
|
||||
return (
|
||||
<WebsiteProvider websiteId={websiteId}>
|
||||
<WebsiteSettings websiteId={websiteId} />
|
||||
</WebsiteProvider>
|
||||
);
|
||||
}
|
||||
12
src/app/(main)/admin/websites/[websiteId]/page.tsx
Normal file
12
src/app/(main)/admin/websites/[websiteId]/page.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { AdminWebsitePage } from './AdminWebsitePage';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||
const { websiteId } = await params;
|
||||
|
||||
return <AdminWebsitePage websiteId={websiteId} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Website',
|
||||
};
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
.filters {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.tag {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import { Button, Icon, Text } from '@umami/react-zen';
|
||||
import { Close } from '@/components/icons';
|
||||
import styles from './WebsiteTags.module.css';
|
||||
|
||||
export function WebsiteTags({
|
||||
items = [],
|
||||
websites = [],
|
||||
onClick,
|
||||
}: {
|
||||
items: any[];
|
||||
websites: any[];
|
||||
onClick: (e: Event) => void;
|
||||
}) {
|
||||
if (websites.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.filters}>
|
||||
{websites.map(websiteId => {
|
||||
const website = items.find(a => a.id === websiteId);
|
||||
|
||||
return (
|
||||
<div key={websiteId} className={styles.tag}>
|
||||
<Button onPress={() => onClick(websiteId)} variant="primary" size="sm">
|
||||
<Text>
|
||||
<b>{`${website.name}`}</b>
|
||||
</Text>
|
||||
<Icon>
|
||||
<Close />
|
||||
</Icon>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -19,19 +19,21 @@ export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?:
|
|||
const { touch } = useModified();
|
||||
const { teamId, renderUrl } = useNavigation();
|
||||
const router = useRouter();
|
||||
const { data } = useUserTeamsQuery(user.id);
|
||||
const { data: teams } = useUserTeamsQuery(user.id);
|
||||
|
||||
const canTransferWebsite =
|
||||
(
|
||||
!teamId &&
|
||||
data.filter(({ teamUser }) =>
|
||||
teamUser.find(
|
||||
({ role, userId }) =>
|
||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||
),
|
||||
)
|
||||
(!teamId &&
|
||||
teams?.data?.filter(({ teamUser }) =>
|
||||
teamUser.find(
|
||||
({ role, userId }) =>
|
||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||
),
|
||||
)) ||
|
||||
[]
|
||||
).length > 0 ||
|
||||
(teamId &&
|
||||
!!data
|
||||
!!teams?.data
|
||||
?.find(({ id }) => id === teamId)
|
||||
?.teamUser.find(({ role, userId }) => role === ROLES.teamOwner && userId === user.id));
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,13 @@ import {
|
|||
Switch,
|
||||
FormSubmitButton,
|
||||
Column,
|
||||
Icon,
|
||||
Grid,
|
||||
Label,
|
||||
useToast,
|
||||
TooltipTrigger,
|
||||
Tooltip,
|
||||
Row,
|
||||
} from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { getRandomChars } from '@/lib/crypto';
|
||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||
import { Refresh } from '@/components/icons';
|
||||
|
||||
const generateId = () => getRandomChars(16);
|
||||
|
||||
|
|
@ -70,24 +66,19 @@ export function WebsiteShareForm({ websiteId, shareId, onSave, onClose }: Websit
|
|||
{id && (
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.shareUrl)}</Label>
|
||||
<Grid columns="1fr auto" gap>
|
||||
<TextField value={url} isReadOnly allowCopy />
|
||||
<TooltipTrigger>
|
||||
<Button onPress={handleGenerate} variant="quiet" size="sm">
|
||||
<Icon>
|
||||
<Refresh />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Tooltip>{formatMessage(labels.regenerate)}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</Grid>
|
||||
<TextField value={url} isReadOnly allowCopy />
|
||||
</Column>
|
||||
)}
|
||||
<FormButtons>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<FormSubmitButton isDisabled={false} isLoading={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
</FormSubmitButton>
|
||||
<FormButtons justifyContent="space-between">
|
||||
<Row>
|
||||
{id && <Button onPress={handleGenerate}>{formatMessage(labels.regenerate)}</Button>}
|
||||
</Row>
|
||||
<Row>
|
||||
{onClose && <Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>}
|
||||
<FormSubmitButton isDisabled={false} isLoading={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</Row>
|
||||
</FormButtons>
|
||||
</Column>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -31,15 +31,16 @@ export function WebsiteTransferForm({
|
|||
const { mutate, error } = useMutation({
|
||||
mutationFn: (data: any) => post(`/websites/${websiteId}/transfer`, data),
|
||||
});
|
||||
const { result, query } = useUserTeamsQuery(user.id);
|
||||
const { data: teams, isLoading } = useUserTeamsQuery(user.id);
|
||||
const isTeamWebsite = !!website?.teamId;
|
||||
|
||||
const items = result.data.filter(({ teamUser }) =>
|
||||
teamUser.find(
|
||||
({ role, userId }) =>
|
||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||
),
|
||||
);
|
||||
const items =
|
||||
teams?.data?.filter(({ teamUser }) =>
|
||||
teamUser.find(
|
||||
({ role, userId }) =>
|
||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||
),
|
||||
) || [];
|
||||
|
||||
const handleSubmit = async () => {
|
||||
mutate(
|
||||
|
|
@ -60,7 +61,7 @@ export function WebsiteTransferForm({
|
|||
setTeamId(key as string);
|
||||
};
|
||||
|
||||
if (query.isLoading) {
|
||||
if (isLoading) {
|
||||
return <Loading icon="dots" position="center" />;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { useContext, useState } from 'react';
|
||||
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||
import { Users } from '@/components/icons';
|
||||
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
|
||||
import { TeamManage } from './TeamManage';
|
||||
import { TeamEditForm } from './TeamEditForm';
|
||||
import { TeamWebsitesDataTable } from '@/app/(main)/teams/[teamId]/settings/websites/TeamWebsitesDataTable';
|
||||
import { TeamMembersDataTable } from '@/app/(main)/teams/[teamId]/settings/members/TeamMembersDataTable';
|
||||
|
||||
export function TeamDetails({ teamId }: { teamId: string }) {
|
||||
const team = useContext(TeamContext);
|
||||
|
|
@ -26,17 +29,25 @@ export function TeamDetails({ teamId }: { teamId: string }) {
|
|||
|
||||
return (
|
||||
<Column gap>
|
||||
<SectionHeader title={team?.name}>
|
||||
<SectionHeader title={team?.name} icon={<Users />}>
|
||||
{!isTeamOwner && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
||||
</SectionHeader>
|
||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
<Tab id="members">{formatMessage(labels.members)}</Tab>
|
||||
<Tab id="websites">{formatMessage(labels.websites)}</Tab>
|
||||
{isTeamOwner && <Tab id="manage">{formatMessage(labels.manage)}</Tab>}
|
||||
</TabList>
|
||||
<TabPanel id="details">
|
||||
<TeamEditForm teamId={teamId} allowEdit={canEdit} />
|
||||
</TabPanel>
|
||||
<TabPanel id="members">
|
||||
<TeamMembersDataTable teamId={teamId} allowEdit />
|
||||
</TabPanel>
|
||||
<TabPanel id="websites">
|
||||
<TeamWebsitesDataTable teamId={teamId} allowEdit />
|
||||
</TabPanel>
|
||||
<TabPanel id="manage">
|
||||
<TeamManage teamId={teamId} />
|
||||
</TabPanel>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
TextField,
|
||||
Button,
|
||||
useToast,
|
||||
Text,
|
||||
} from '@umami/react-zen';
|
||||
import { getRandomChars } from '@/lib/crypto';
|
||||
import { useContext } from 'react';
|
||||
|
|
@ -47,8 +48,7 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
|
|||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
{allowEdit && <TextField />}
|
||||
{!allowEdit && team?.name}
|
||||
{allowEdit ? <TextField /> : <Text>{team?.name}</Text>}
|
||||
</FormField>
|
||||
{!cloudMode && allowEdit && (
|
||||
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ export async function GET(request: Request) {
|
|||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
password: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function SectionHeader({
|
|||
return (
|
||||
<Row {...props} justifyContent="space-between" alignItems="center" height="60px">
|
||||
<Row gap="3" alignItems="center">
|
||||
{icon && <Icon>{icon}</Icon>}
|
||||
{icon && <Icon size="md">{icon}</Icon>}
|
||||
{title && <Heading size="3">{title}</Heading>}
|
||||
{description && <Text color="muted">{description}</Text>}
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { useModified } from '../useModified';
|
||||
import { usePagedQuery } from '@/components/hooks';
|
||||
import { usePagedQuery } from '../usePagedQuery';
|
||||
import { ReactQueryOptions } from '@/lib/types';
|
||||
|
||||
export function useTeamsQuery(params?: Record<string, any>, options?: ReactQueryOptions) {
|
||||
|
|
@ -8,7 +8,7 @@ export function useTeamsQuery(params?: Record<string, any>, options?: ReactQuery
|
|||
const { modified } = useModified(`teams`);
|
||||
|
||||
return usePagedQuery({
|
||||
queryKey: ['websites', { modified, ...params }],
|
||||
queryKey: ['teams:admin', { modified, ...params }],
|
||||
queryFn: pageParams => {
|
||||
return get(`/admin/teams`, {
|
||||
...params,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export function useUsersQuery() {
|
|||
const { modified } = useModified(`users`);
|
||||
|
||||
return usePagedQuery({
|
||||
queryKey: ['users', { modified }],
|
||||
queryKey: ['users:admin', { modified }],
|
||||
queryFn: (pageParams: any) => {
|
||||
return get('/admin/users', {
|
||||
...pageParams,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function useWebsitesQuery(params?: Record<string, any>, options?: ReactQu
|
|||
const { modified } = useModified(`websites`);
|
||||
|
||||
return usePagedQuery({
|
||||
queryKey: ['websites', { modified, ...params }],
|
||||
queryKey: ['websites:admin', { modified, ...params }],
|
||||
queryFn: pageParams => {
|
||||
return get(`/admin/websites`, {
|
||||
...pageParams,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export * from '@/app/(main)/settings/teams/TeamsDataTable';
|
|||
export * from '@/app/(main)/settings/teams/TeamsHeader';
|
||||
export * from '@/app/(main)/settings/teams/TeamsJoinButton';
|
||||
export * from '@/app/(main)/settings/teams/TeamsTable';
|
||||
export * from '@/app/(main)/settings/teams/WebsiteTags';
|
||||
|
||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
||||
export * from '@/app/(main)/settings/websites/[websiteId]/WebsiteTrackingCode';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue