mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-15 09:45:04 +01:00
Fix joining team dupe. Add loading to team member remove. Fix messages.
This commit is contained in:
parent
a22d50a597
commit
6d5aeb3bd1
@ -7,6 +7,7 @@ export const labels = defineMessages({
|
||||
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
|
||||
continue: { id: 'label.continue', defaultMessage: 'Continue' },
|
||||
delete: { id: 'label.delete', defaultMessage: 'Delete' },
|
||||
leave: { id: 'label.leave', defaultMessage: 'Leave' },
|
||||
users: { id: 'label.users', defaultMessage: 'Users' },
|
||||
createUser: { id: 'label.create-user', defaultMessage: 'Create user' },
|
||||
username: { id: 'label.username', defaultMessage: 'Username' },
|
||||
@ -67,6 +68,7 @@ export const labels = defineMessages({
|
||||
dateRange: { id: 'label.date-range', defaultMessage: 'Date range' },
|
||||
viewDetails: { id: 'label.view-details', defaultMessage: 'View details' },
|
||||
deleteTeam: { id: 'label.delete-team', defaultMessage: 'Delete team' },
|
||||
leaveTeam: { id: 'label.leave-team', defaultMessage: 'Leave team' },
|
||||
refresh: { id: 'label.refresh', defaultMessage: 'Refresh' },
|
||||
pages: { id: 'label.pages', defaultMessage: 'Pages' },
|
||||
referrers: { id: 'label.referrers', defaultMessage: 'Referrers' },
|
||||
@ -123,6 +125,10 @@ export const messages = defineMessages({
|
||||
id: 'message.delete-user-warning',
|
||||
defaultMessage: 'Are you sure you want to delete the user {username}?',
|
||||
},
|
||||
leaveTeamWarning: {
|
||||
id: 'message.leave-team-warning',
|
||||
defaultMessage: 'Are you sure you want to leave the team {name}?',
|
||||
},
|
||||
deleteTeamWarning: {
|
||||
id: 'message.delete-team-warning',
|
||||
defaultMessage: 'Are you sure you want to delete the team {name}?',
|
||||
|
32
components/pages/settings/teams/TeamLeaveForm.js
Normal file
32
components/pages/settings/teams/TeamLeaveForm.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function TeamLeaveForm({ teamUserId, teamName, onSave, onClose }) {
|
||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, error, isLoading } = useMutation(data => del(`/teamUsers/${teamUserId}`, data));
|
||||
|
||||
const handleSubmit = async data => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
onSave();
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error}>
|
||||
<p>
|
||||
<FormattedMessage {...messages.leaveTeamWarning} values={{ name: <b>{teamName}</b> }} />
|
||||
</p>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="danger" disabled={isLoading}>
|
||||
{formatMessage(labels.leave)}
|
||||
</SubmitButton>
|
||||
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
}
|
@ -6,7 +6,7 @@ import useMessages from 'hooks/useMessages';
|
||||
export default function TeamMembers({ teamId, readOnly }) {
|
||||
const { toast, showToast } = useToast();
|
||||
const { get, useQuery } = useApi();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { formatMessage, messages } = useMessages();
|
||||
const { data, isLoading, refetch } = useQuery(['teams:users', teamId], () =>
|
||||
get(`/teams/${teamId}/users`),
|
||||
);
|
||||
@ -17,7 +17,7 @@ export default function TeamMembers({ teamId, readOnly }) {
|
||||
|
||||
const handleSave = async () => {
|
||||
await refetch();
|
||||
showToast({ message: formatMessage(labels.saved), variant: 'success' });
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableColumn,
|
||||
Button,
|
||||
LoadingButton,
|
||||
Icon,
|
||||
Icons,
|
||||
Flexbox,
|
||||
@ -15,12 +15,14 @@ import { ROLES } from 'lib/constants';
|
||||
import useUser from 'hooks/useUser';
|
||||
import useApi from 'hooks/useApi';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function TeamMembersTable({ data = [], onSave, readOnly }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate } = useMutation(data => del(`/teamUsers/${data.teamUserId}`));
|
||||
const { mutate, isLoading } = useMutation(data => del(`/teamUsers/${data.teamUserId}`));
|
||||
const [loadingIds, setLoadingIds] = useState([]);
|
||||
|
||||
const columns = [
|
||||
{ name: 'username', label: formatMessage(labels.username), style: { flex: 2 } },
|
||||
@ -29,12 +31,18 @@ export default function TeamMembersTable({ data = [], onSave, readOnly }) {
|
||||
];
|
||||
|
||||
const handleRemoveTeamMember = teamUserId => {
|
||||
setLoadingIds(prev => [...prev, teamUserId]);
|
||||
|
||||
mutate(
|
||||
{ teamUserId },
|
||||
{
|
||||
onSuccess: async () => {
|
||||
onSuccess: () => {
|
||||
setLoadingIds(loadingIds.filter(a => a !== teamUserId));
|
||||
onSave();
|
||||
},
|
||||
onError: () => {
|
||||
setLoadingIds(loadingIds.filter(a => a !== teamUserId));
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
@ -59,15 +67,16 @@ export default function TeamMembersTable({ data = [], onSave, readOnly }) {
|
||||
),
|
||||
action: !readOnly && (
|
||||
<Flexbox flex={1} justifyContent="end">
|
||||
<Button
|
||||
<LoadingButton
|
||||
onClick={() => handleRemoveTeamMember(row.id)}
|
||||
disabled={user.id === row?.user?.id || row.role === ROLES.teamOwner}
|
||||
loading={isLoading && loadingIds.some(a => a === row.id)}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.remove)}</Text>
|
||||
</Button>
|
||||
</LoadingButton>
|
||||
</Flexbox>
|
||||
),
|
||||
};
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
Text,
|
||||
} from 'react-basics';
|
||||
import TeamDeleteForm from './TeamDeleteForm';
|
||||
import TeamLeaveForm from './TeamLeaveForm';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { ROLES } from 'lib/constants';
|
||||
@ -42,9 +43,10 @@ export default function TeamsTable({ data = [], onDelete }) {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{(row, keys, rowIndex) => {
|
||||
const { id } = row;
|
||||
const { id, teamUser } = row;
|
||||
const owner = row.teamUser.find(({ role }) => role === ROLES.teamOwner);
|
||||
const showDelete = user.id === owner?.userId;
|
||||
const teamUserId = teamUser.find(a => a.userId === user.id).id;
|
||||
|
||||
const rowData = {
|
||||
...row,
|
||||
@ -54,9 +56,9 @@ export default function TeamsTable({ data = [], onDelete }) {
|
||||
<Link href={`/settings/teams/${id}`}>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
<Icons.Show />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
{showDelete && (
|
||||
@ -79,6 +81,26 @@ export default function TeamsTable({ data = [], onDelete }) {
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
)}
|
||||
{!showDelete && (
|
||||
<ModalTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.leave)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.leaveTeam)}>
|
||||
{close => (
|
||||
<TeamLeaveForm
|
||||
teamUserId={teamUserId}
|
||||
teamName={row.name}
|
||||
onSave={onDelete}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
)}
|
||||
</Flexbox>
|
||||
),
|
||||
};
|
||||
|
@ -175,6 +175,10 @@ export async function canDeleteTeamUser({ user }: Auth, teamUserId: string) {
|
||||
if (validate(teamUserId)) {
|
||||
const removeUser = await getTeamUserById(teamUserId);
|
||||
|
||||
if (removeUser.userId === user.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const teamUser = await getTeamUser(removeUser.teamId, user.id);
|
||||
|
||||
return hasPermission(teamUser.role, PERMISSIONS.teamUpdate);
|
||||
|
@ -3,7 +3,7 @@ import { NextApiRequestQueryBody } from 'lib/types';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { methodNotAllowed, ok, notFound } from 'next-basics';
|
||||
import { createTeamUser, getTeam } from 'queries';
|
||||
import { createTeamUser, getTeam, getTeamUser } from 'queries';
|
||||
import { ROLES } from 'lib/constants';
|
||||
|
||||
export interface TeamsJoinRequestBody {
|
||||
@ -25,6 +25,12 @@ export default async (
|
||||
return notFound(res, 'message.team-not-found');
|
||||
}
|
||||
|
||||
const teamUser = await getTeamUser(team.id, req.auth.user.id);
|
||||
|
||||
if (teamUser) {
|
||||
return methodNotAllowed(res, 'message.team-already-member');
|
||||
}
|
||||
|
||||
await createTeamUser(req.auth.user.id, team.id, ROLES.teamMember);
|
||||
|
||||
return ok(res, team);
|
||||
|
Loading…
Reference in New Issue
Block a user