Refactored teams components.

This commit is contained in:
Mike Cao 2023-10-07 22:42:49 -07:00
parent 6253d55790
commit 8b48130d5f
18 changed files with 75 additions and 44 deletions

View File

@ -6,7 +6,9 @@ import { useMessages, useLocale } from 'components/hooks';
import { formatDate } from 'lib/date'; import { formatDate } from 'lib/date';
import styles from './RetentionTable.module.css'; import styles from './RetentionTable.module.css';
export function RetentionTable() { const DAYS = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
export function RetentionTable({ days = DAYS }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { locale } = useLocale(); const { locale } = useLocale();
const { report } = useContext(ReportContext); const { report } = useContext(ReportContext);
@ -16,8 +18,6 @@ export function RetentionTable() {
return <EmptyPlaceholder />; return <EmptyPlaceholder />;
} }
const days = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
const rows = data.reduce((arr, row) => { const rows = data.reduce((arr, row) => {
const { date, visitors, day } = row; const { date, visitors, day } = row;
if (day === 0) { if (day === 0) {

View File

@ -9,7 +9,7 @@ export default function SettingsLayout({ children }) {
const { user } = useUser(); const { user } = useUser();
const pathname = usePathname(); const pathname = usePathname();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const cloudMode = Boolean(process.env.cloudMode); const cloudMode = !!process.env.cloudMode;
const items = [ const items = [
{ key: 'websites', label: formatMessage(labels.websites), url: '/settings/websites' }, { key: 'websites', label: formatMessage(labels.websites), url: '/settings/websites' },
@ -20,6 +20,10 @@ export default function SettingsLayout({ children }) {
const getKey = () => items.find(({ url }) => pathname === url)?.key; const getKey = () => items.find(({ url }) => pathname === url)?.key;
if (cloudMode) {
return null;
}
return ( return (
<div className={styles.layout}> <div className={styles.layout}>
{!cloudMode && ( {!cloudMode && (

View File

@ -1,5 +1,6 @@
import ProfileHeader from './ProfileHeader'; import ProfileHeader from './ProfileHeader';
import ProfileSettings from './ProfileSettings'; import ProfileSettings from './ProfileSettings';
import { Metadata } from 'next';
export default function () { export default function () {
return ( return (
@ -9,3 +10,7 @@ export default function () {
</> </>
); );
} }
export const metadata: Metadata = {
title: 'Profile Settings | umami',
};

View File

@ -10,6 +10,7 @@ import {
} from 'react-basics'; } from 'react-basics';
import useApi from 'components/hooks/useApi'; import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import { setValue } from 'store/cache';
export function TeamJoinForm({ onSave, onClose }) { export function TeamJoinForm({ onSave, onClose }) {
const { formatMessage, labels, getMessage } = useMessages(); const { formatMessage, labels, getMessage } = useMessages();
@ -20,8 +21,9 @@ export function TeamJoinForm({ onSave, onClose }) {
const handleSubmit = async data => { const handleSubmit = async data => {
mutate(data, { mutate(data, {
onSuccess: async () => { onSuccess: async () => {
onSave(); setValue('teams', Date.now());
onClose(); onSave?.();
onClose?.();
}, },
}); });
}; };

View File

@ -13,7 +13,7 @@ export function TeamLeaveButton({ teamId, teamName, onLeave }) {
<ModalTrigger> <ModalTrigger>
<Button> <Button>
<Icon rotate={dir === 'rtl' ? 180 : 0}> <Icon rotate={dir === 'rtl' ? 180 : 0}>
<Icons.ArrowRight /> <Icons.Logout />
</Icon> </Icon>
<Text>{formatMessage(labels.leave)}</Text> <Text>{formatMessage(labels.leave)}</Text>
</Button> </Button>

View File

@ -3,10 +3,12 @@ import DataTable from 'components/common/DataTable';
import TeamsTable from 'app/(main)/settings/teams/TeamsTable'; import TeamsTable from 'app/(main)/settings/teams/TeamsTable';
import useApi from 'components/hooks/useApi'; import useApi from 'components/hooks/useApi';
import useFilterQuery from 'components/hooks/useFilterQuery'; import useFilterQuery from 'components/hooks/useFilterQuery';
import useCache from 'store/cache';
export function TeamsDataTable() { export function TeamsDataTable() {
const { get } = useApi(); const { get } = useApi();
const queryResult = useFilterQuery(['teams'], params => { const modified = useCache(state => state?.websites);
const queryResult = useFilterQuery(['teams', { modified }], params => {
return get(`/teams`, { return get(`/teams`, {
...params, ...params,
}); });

View File

@ -21,18 +21,18 @@ export function TeamsTable({ data = [] }) {
{row => { {row => {
const { id, name, teamUser } = row; const { id, name, teamUser } = row;
const owner = teamUser.find(({ role }) => role === ROLES.teamOwner); const owner = teamUser.find(({ role }) => role === ROLES.teamOwner);
const showDelete = user.id === owner?.userId; const isOwner = user.id === owner?.userId;
return ( return (
<> <>
{showDelete && <TeamDeleteButton teamId={id} teamName={name} />} {isOwner && <TeamDeleteButton teamId={id} teamName={name} />}
{!showDelete && <TeamLeaveButton teamId={id} teamName={name} />} {!isOwner && <TeamLeaveButton teamId={id} teamName={name} />}
<Link href={`/settings/teams/${id}`}> <Link href={`/settings/teams/${id}`}>
<Button> <Button>
<Icon> <Icon>
<Icons.Edit /> <Icons.Edit />
</Icon> </Icon>
<Text>{formatMessage(labels.edit)}</Text> <Text>{formatMessage(isOwner ? labels.edit : labels.view)}</Text>
</Button> </Button>
</Link> </Link>
</> </>

View File

@ -3,12 +3,13 @@ import { useState } from 'react';
import { Button, Form, FormButtons, GridColumn, Loading, SubmitButton, Toggle } from 'react-basics'; import { Button, Form, FormButtons, GridColumn, Loading, SubmitButton, Toggle } from 'react-basics';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import WebsitesDataTable from '../../websites/WebsitesDataTable'; import WebsitesDataTable from '../../websites/WebsitesDataTable';
import Empty from 'components/common/Empty';
export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { export function TeamWebsiteAddForm({ teamId, onSave, onClose }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { get, post, useQuery, useMutation } = useApi(); const { get, post, useQuery, useMutation } = useApi();
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
const { data: websites } = useQuery(['websites'], () => get('/websites')); const { data: websites, isLoading } = useQuery(['websites'], () => get('/websites'));
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const hasData = websites && websites.data.length > 0; const hasData = websites && websites.data.length > 0;
@ -30,7 +31,8 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) {
return ( return (
<> <>
{!hasData && <Loading />} {isLoading && !hasData && <Loading />}
{!isLoading && !hasData && <Empty />}
{hasData && ( {hasData && (
<Form onSubmit={handleSubmit} error={error}> <Form onSubmit={handleSubmit} error={error}>
<WebsitesDataTable showHeader={false} showActions={false}> <WebsitesDataTable showHeader={false} showActions={false}>

View File

@ -1,5 +1,6 @@
import TeamsDataTable from './TeamsDataTable'; import TeamsDataTable from './TeamsDataTable';
import TeamsHeader from './TeamsHeader'; import TeamsHeader from './TeamsHeader';
import { Metadata } from 'next';
export default function () { export default function () {
if (process.env.cloudMode) { if (process.env.cloudMode) {
@ -13,3 +14,7 @@ export default function () {
</> </>
); );
} }
export const metadata: Metadata = {
title: 'Teams Settings | umami',
};

View File

@ -2,10 +2,6 @@ import UsersDataTable from './UsersDataTable';
import { Metadata } from 'next'; import { Metadata } from 'next';
export default function () { export default function () {
if (process.env.cloudMode) {
return null;
}
return <UsersDataTable />; return <UsersDataTable />;
} }
export const metadata: Metadata = { export const metadata: Metadata = {

View File

@ -1,4 +1,5 @@
'use client'; 'use client';
import { ReactNode } from 'react';
import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable'; import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable';
import useUser from 'components/hooks/useUser'; import useUser from 'components/hooks/useUser';
import useApi from 'components/hooks/useApi'; import useApi from 'components/hooks/useApi';
@ -6,10 +7,20 @@ import DataTable from 'components/common/DataTable';
import useFilterQuery from 'components/hooks/useFilterQuery'; import useFilterQuery from 'components/hooks/useFilterQuery';
import useCache from 'store/cache'; import useCache from 'store/cache';
export interface WebsitesDataTableProps {
allowEdit?: boolean;
allowView?: boolean;
showActions?: boolean;
showTeam?: boolean;
includeTeams?: boolean;
onlyTeams?: boolean;
children?: ReactNode;
}
function useWebsites({ includeTeams, onlyTeams }) { function useWebsites({ includeTeams, onlyTeams }) {
const { user } = useUser(); const { user } = useUser();
const { get } = useApi(); const { get } = useApi();
const modified = useCache(state => state?.websites); const modified = useCache((state: any) => state?.websites);
return useFilterQuery( return useFilterQuery(
['websites', { includeTeams, onlyTeams, modified }], ['websites', { includeTeams, onlyTeams, modified }],
@ -32,7 +43,7 @@ export function WebsitesDataTable({
includeTeams, includeTeams,
onlyTeams, onlyTeams,
children, children,
}) { }: WebsitesDataTableProps) {
const queryResult = useWebsites({ includeTeams, onlyTeams }); const queryResult = useWebsites({ includeTeams, onlyTeams });
return ( return (

View File

@ -3,12 +3,12 @@ import useMessages from 'components/hooks/useMessages';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
import WebsiteAddButton from './WebsiteAddButton'; import WebsiteAddButton from './WebsiteAddButton';
export function WebsitesHeader() { export function WebsitesHeader({ showActions = true }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
return ( return (
<PageHeader title={formatMessage(labels.websites)}> <PageHeader title={formatMessage(labels.websites)}>
{!process.env.cloudMode && <WebsiteAddButton />} {!process.env.cloudMode && showActions && <WebsiteAddButton />}
</PageHeader> </PageHeader>
); );
} }

View File

@ -1,11 +1,8 @@
import WebsitesDataTable from './WebsitesDataTable'; import WebsitesDataTable from './WebsitesDataTable';
import WebsitesHeader from './WebsitesHeader'; import WebsitesHeader from './WebsitesHeader';
import { Metadata } from 'next';
export default function () { export default function () {
if (process.env.cloudMode) {
return null;
}
return ( return (
<> <>
<WebsitesHeader /> <WebsitesHeader />
@ -13,3 +10,7 @@ export default function () {
</> </>
); );
} }
export const metadata: Metadata = {
title: 'Websites Settings | umami',
};

View File

@ -1,5 +1,5 @@
'use client'; 'use client';
import WebsiteList from '../settings/websites/WebsitesDataTable'; import WebsitesDataTable from '../settings/websites/WebsitesDataTable';
import { useMessages } from 'components/hooks'; import { useMessages } from 'components/hooks';
import { useState } from 'react'; import { useState } from 'react';
import { Item, Tabs } from 'react-basics'; import { Item, Tabs } from 'react-basics';
@ -12,6 +12,7 @@ const TABS = {
export function WebsitesBrowse() { export function WebsitesBrowse() {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const [tab, setTab] = useState(TABS.myWebsites); const [tab, setTab] = useState(TABS.myWebsites);
const allowEdit = !process.env.cloudMode;
return ( return (
<> <>
@ -19,9 +20,9 @@ export function WebsitesBrowse() {
<Item key={TABS.myWebsites}>{formatMessage(labels.myWebsites)}</Item> <Item key={TABS.myWebsites}>{formatMessage(labels.myWebsites)}</Item>
<Item key={TABS.teamWebsites}>{formatMessage(labels.teamWebsites)}</Item> <Item key={TABS.teamWebsites}>{formatMessage(labels.teamWebsites)}</Item>
</Tabs> </Tabs>
{tab === TABS.myWebsites && <WebsiteList showHeader={false} />} {tab === TABS.myWebsites && <WebsitesDataTable allowEdit={allowEdit} />}
{tab === TABS.teamWebsites && ( {tab === TABS.teamWebsites && (
<WebsiteList showHeader={false} showTeam={true} onlyTeams={true} /> <WebsitesDataTable showTeam={true} onlyTeams={true} allowEdit={allowEdit} />
)} )}
</> </>
); );

View File

@ -1,12 +0,0 @@
'use client';
import WebsitesHeader from '../../(main)/settings/websites/WebsitesHeader';
import WebsitesBrowse from './WebsitesBrowse';
export default function WebsitesPage() {
return (
<>
<WebsitesHeader />
<WebsitesBrowse />
</>
);
}

View File

@ -0,0 +1,16 @@
import WebsitesHeader from 'app/(main)/settings/websites/WebsitesHeader';
import WebsitesBrowse from './WebsitesBrowse';
import { Metadata } from 'next';
export default function WebsitesPage() {
return (
<>
<WebsitesHeader showActions={false} />
<WebsitesBrowse />
</>
);
}
export const metadata: Metadata = {
title: 'Websites | umami',
};

View File

@ -45,7 +45,7 @@ export function DataTable({
const noResults = Boolean(!isLoading && query && !hasData); const noResults = Boolean(!isLoading && query && !hasData);
const handleSearch = query => { const handleSearch = query => {
setParams({ ...params, query }); setParams({ ...params, query, page: params.query ? page : 1 });
}; };
const handlePageChange = page => { const handlePageChange = page => {

View File

@ -14,8 +14,6 @@ export interface TeamsRequestBody {
name: string; name: string;
} }
export interface MyTeamsRequestQuery extends SearchFilter {}
const schema = { const schema = {
GET: yup.object().shape({ GET: yup.object().shape({
...pageInfo, ...pageInfo,