mirror of
https://github.com/umami-software/umami.git
synced 2026-02-09 23:27:12 +01:00
Update teams features.
This commit is contained in:
parent
89f2fd601e
commit
656df4f846
23 changed files with 278 additions and 113 deletions
43
components/pages/settings/teams/JoinTeamForm.js
Normal file
43
components/pages/settings/teams/JoinTeamForm.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { useRef } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
FormInput,
|
||||
FormButtons,
|
||||
TextField,
|
||||
Button,
|
||||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import { labels, getMessage } from 'components/messages';
|
||||
|
||||
export default function TeamJoinForm({ onSave, onClose }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post('/teams/join', data));
|
||||
const ref = useRef(null);
|
||||
|
||||
const handleSubmit = async data => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
onSave();
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error && getMessage(error, formatMessage)}>
|
||||
<FormRow label={formatMessage(labels.accessCode)}>
|
||||
<FormInput name="accessCode" rules={{ required: formatMessage(labels.required) }}>
|
||||
<TextField autoComplete="off" />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="primary">{formatMessage(labels.join)}</SubmitButton>
|
||||
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable';
|
|||
|
||||
export default function TeamMembers({ teamId }) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading } = useQuery(['team-members', teamId], () =>
|
||||
const { data, isLoading } = useQuery(['teams:users', teamId], () =>
|
||||
get(`/teams/${teamId}/users`),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,14 +40,16 @@ export default function TeamSettings({ teamId }) {
|
|||
return (
|
||||
<Page loading={isLoading || !values}>
|
||||
{toast}
|
||||
<PageHeader>
|
||||
<Breadcrumbs>
|
||||
<Item>
|
||||
<Link href="/settings/teams">Teams</Link>
|
||||
</Item>
|
||||
<Item>{values?.name}</Item>
|
||||
</Breadcrumbs>
|
||||
</PageHeader>
|
||||
<PageHeader
|
||||
title={
|
||||
<Breadcrumbs>
|
||||
<Item>
|
||||
<Link href="/settings/teams">Teams</Link>
|
||||
</Item>
|
||||
<Item>{values?.name}</Item>
|
||||
</Breadcrumbs>
|
||||
}
|
||||
/>
|
||||
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30 }}>
|
||||
<Item key="details">{formatMessage(labels.details)}</Item>
|
||||
<Item key="members">{formatMessage(labels.members)}</Item>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { messages } from 'components/messages';
|
|||
export default function TeamWebsites({ teamId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading } = useQuery(['teams/websites', teamId], () =>
|
||||
const { data, isLoading } = useQuery(['teams:websites', teamId], () =>
|
||||
get(`/teams/${teamId}/websites`),
|
||||
);
|
||||
const hasData = data && data.length !== 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, Icon, Modal, ModalTrigger, useToast, Icons, Text } from 'react-basics';
|
||||
import { Button, Icon, Modal, ModalTrigger, useToast, Text, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useApi from 'hooks/useApi';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
|
|
@ -8,6 +8,8 @@ import PageHeader from 'components/layout/PageHeader';
|
|||
import TeamsTable from 'components/pages/settings/teams/TeamsTable';
|
||||
import Page from 'components/layout/Page';
|
||||
import { labels, messages } from 'components/messages';
|
||||
import Icons from 'components/icons';
|
||||
import TeamJoinForm from './JoinTeamForm';
|
||||
|
||||
export default function TeamsList() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
|
@ -22,6 +24,24 @@ export default function TeamsList() {
|
|||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
};
|
||||
|
||||
const handleJoin = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
};
|
||||
|
||||
const joinButton = (
|
||||
<ModalTrigger>
|
||||
<Button variant="secondary">
|
||||
<Icon>
|
||||
<Icons.AddUser />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.joinTeam)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.joinTeam)}>
|
||||
{close => <TeamJoinForm onSave={handleJoin} onClose={close} />}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
);
|
||||
|
||||
const createButton = (
|
||||
<ModalTrigger>
|
||||
<Button variant="primary">
|
||||
|
|
@ -39,7 +59,14 @@ export default function TeamsList() {
|
|||
return (
|
||||
<Page loading={isLoading} error={error}>
|
||||
{toast}
|
||||
<PageHeader title={formatMessage(labels.team)}>{createButton}</PageHeader>
|
||||
<PageHeader title={formatMessage(labels.team)}>
|
||||
{hasData && (
|
||||
<Flexbox gap={10}>
|
||||
{joinButton}
|
||||
{createButton}
|
||||
</Flexbox>
|
||||
)}
|
||||
</PageHeader>
|
||||
{hasData && <TeamsTable data={data} />}
|
||||
{!hasData && (
|
||||
<EmptyPlaceholder message={formatMessage(messages.noTeams)}>
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ import {
|
|||
} from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { labels } from 'components/messages';
|
||||
import { ROLES } from 'lib/constants';
|
||||
|
||||
export default function TeamsTable({ data = [] }) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const columns = [
|
||||
{ name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
|
||||
{ name: 'owner', label: formatMessage(labels.owner) },
|
||||
{ name: 'action', label: ' ' },
|
||||
];
|
||||
|
||||
|
|
@ -38,23 +40,27 @@ export default function TeamsTable({ data = [] }) {
|
|||
{(row, keys, rowIndex) => {
|
||||
const { id } = row;
|
||||
|
||||
row.action = (
|
||||
<Flexbox flex={1} justifyContent="end">
|
||||
<Link href={`/settings/teams/${id}`}>
|
||||
<a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.settings)}</Text>
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</Flexbox>
|
||||
);
|
||||
const rowData = {
|
||||
...row,
|
||||
owner: row.teamUsers.find(({ role }) => role === ROLES.teamOwner)?.user?.username,
|
||||
action: (
|
||||
<Flexbox flex={1} justifyContent="end">
|
||||
<Link href={`/settings/teams/${id}`}>
|
||||
<a>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.settings)}</Text>
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</Flexbox>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow key={rowIndex} data={row} keys={keys}>
|
||||
<TableRow key={rowIndex} data={rowData} keys={keys}>
|
||||
{(data, key, colIndex) => {
|
||||
return (
|
||||
<TableCell key={colIndex} style={{ ...columns[colIndex]?.style }}>
|
||||
|
|
|
|||
|
|
@ -46,14 +46,16 @@ export default function UserSettings({ userId }) {
|
|||
return (
|
||||
<Page loading={isLoading || !values}>
|
||||
{toast}
|
||||
<PageHeader>
|
||||
<Breadcrumbs>
|
||||
<Item>
|
||||
<Link href="/settings/users">{formatMessage(labels.users)}</Link>
|
||||
</Item>
|
||||
<Item>{values?.username}</Item>
|
||||
</Breadcrumbs>
|
||||
</PageHeader>
|
||||
<PageHeader
|
||||
title={
|
||||
<Breadcrumbs>
|
||||
<Item>
|
||||
<Link href="/settings/users">{formatMessage(labels.users)}</Link>
|
||||
</Item>
|
||||
<Item>{values?.username}</Item>
|
||||
</Breadcrumbs>
|
||||
}
|
||||
/>
|
||||
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
|
||||
<Item key="details">{formatMessage(labels.details)}</Item>
|
||||
<Item key="websites">{formatMessage(labels.websites)}</Item>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { messages } from 'components/messages';
|
|||
export default function UserWebsites({ userId }) {
|
||||
const { formatMessage } = useIntl();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data, isLoading } = useQuery(['user/websites', userId], () =>
|
||||
const { data, isLoading } = useQuery(['user:websites', userId], () =>
|
||||
get(`/users/${userId}/websites`),
|
||||
);
|
||||
const hasData = data && data.length !== 0;
|
||||
|
|
|
|||
|
|
@ -49,13 +49,16 @@ export default function WebsiteSettings({ websiteId }) {
|
|||
return (
|
||||
<Page loading={isLoading || !values}>
|
||||
{toast}
|
||||
<PageHeader>
|
||||
<Breadcrumbs>
|
||||
<Item>
|
||||
<Link href="/settings/websites">{formatMessage(labels.websites)}</Link>
|
||||
</Item>
|
||||
<Item>{values?.name}</Item>
|
||||
</Breadcrumbs>
|
||||
<PageHeader
|
||||
title={
|
||||
<Breadcrumbs>
|
||||
<Item>
|
||||
<Link href="/settings/websites">{formatMessage(labels.websites)}</Link>
|
||||
</Item>
|
||||
<Item>{values?.name}</Item>
|
||||
</Breadcrumbs>
|
||||
}
|
||||
>
|
||||
<Link href={`/websites/${websiteId}`}>
|
||||
<a>
|
||||
<Button variant="primary">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue