Fix joining team dupe. Add loading to team member remove. Fix messages.

This commit is contained in:
Brian Cao 2023-03-29 16:02:14 -07:00
parent a22d50a597
commit 6d5aeb3bd1
7 changed files with 90 additions and 11 deletions

View File

@ -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}?',

View 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>
);
}

View File

@ -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 (

View File

@ -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>
),
};

View File

@ -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>
),
};

View File

@ -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);

View File

@ -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);