mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 09:57:00 +01:00
add team member edit and remove confirmation
This commit is contained in:
parent
5ae04291af
commit
c7e9190c95
@ -0,0 +1,49 @@
|
||||
import { useMessages, useModified } from 'components/hooks';
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
||||
import TeamMemberEditForm from './TeamMemberEditForm';
|
||||
|
||||
export function TeamMemberEditButton({
|
||||
teamId,
|
||||
userId,
|
||||
role,
|
||||
onSave,
|
||||
}: {
|
||||
teamId: string;
|
||||
userId: string;
|
||||
role: string;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
touch('teams:members');
|
||||
onSave?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.editMember)}>
|
||||
{(close: () => void) => (
|
||||
<TeamMemberEditForm
|
||||
teamId={teamId}
|
||||
userId={userId}
|
||||
role={role}
|
||||
onSave={handleSave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamMemberEditButton;
|
@ -0,0 +1,78 @@
|
||||
import { useApi, useMessages } from 'components/hooks';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormInput,
|
||||
FormRow,
|
||||
Item,
|
||||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
|
||||
export function UserAddForm({
|
||||
teamId,
|
||||
userId,
|
||||
role,
|
||||
onSave,
|
||||
onClose,
|
||||
}: {
|
||||
teamId: string;
|
||||
userId: string;
|
||||
role: string;
|
||||
onSave?: () => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error, isPending } = useMutation({
|
||||
mutationFn: (data: any) => post(`/teams/${teamId}/users/${userId}`, data),
|
||||
});
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
onSave();
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const renderValue = (value: string) => {
|
||||
if (value === ROLES.teamMember) {
|
||||
return formatMessage(labels.teamMember);
|
||||
}
|
||||
if (value === ROLES.teamViewOnly) {
|
||||
return formatMessage(labels.viewOnly);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error} values={{ role }}>
|
||||
<FormRow label={formatMessage(labels.role)}>
|
||||
<FormInput name="role" rules={{ required: formatMessage(labels.required) }}>
|
||||
<Dropdown
|
||||
renderValue={renderValue}
|
||||
style={{
|
||||
minWidth: '250px',
|
||||
}}
|
||||
>
|
||||
<Item key={ROLES.teamMember}>{formatMessage(labels.teamMember)}</Item>
|
||||
<Item key={ROLES.teamViewOnly}>{formatMessage(labels.viewOnly)}</Item>
|
||||
</Dropdown>
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="primary" disabled={false}>
|
||||
{formatMessage(labels.save)}
|
||||
</SubmitButton>
|
||||
<Button disabled={isPending} onClick={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserAddForm;
|
@ -1,44 +1,64 @@
|
||||
import ConfirmationForm from 'components/common/ConfirmationForm';
|
||||
import { useApi, useMessages, useModified } from 'components/hooks';
|
||||
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
||||
import { messages } from 'components/messages';
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export function TeamMemberRemoveButton({
|
||||
teamId,
|
||||
userId,
|
||||
disabled,
|
||||
userName,
|
||||
onSave,
|
||||
}: {
|
||||
teamId: string;
|
||||
userId: string;
|
||||
userName: string;
|
||||
disabled?: boolean;
|
||||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, isPending } = useMutation({
|
||||
const { mutate, isPending, error } = useMutation({
|
||||
mutationFn: () => del(`/teams/${teamId}/users/${userId}`),
|
||||
});
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleRemoveTeamMember = () => {
|
||||
const handleConfirm = (close: () => void) => {
|
||||
mutate(null, {
|
||||
onSuccess: () => {
|
||||
touch('teams:members');
|
||||
onSave?.();
|
||||
close();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<LoadingButton
|
||||
onClick={() => handleRemoveTeamMember()}
|
||||
disabled={disabled}
|
||||
isLoading={isPending}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.remove)}</Text>
|
||||
</LoadingButton>
|
||||
<ModalTrigger>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.remove)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.removeMember)}>
|
||||
{(close: () => void) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{ target: <b>{userName}</b> }}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.remove)}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { GridColumn, GridTable, useBreakpoint } from 'react-basics';
|
||||
import { useMessages, useLogin } from 'components/hooks';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import TeamMemberRemoveButton from './TeamMemberRemoveButton';
|
||||
import TeamMemberEditButton from './TeamMemberEditButton';
|
||||
|
||||
export function TeamMembersTable({
|
||||
data = [],
|
||||
@ -19,6 +20,7 @@ export function TeamMembersTable({
|
||||
const roles = {
|
||||
[ROLES.teamOwner]: formatMessage(labels.teamOwner),
|
||||
[ROLES.teamMember]: formatMessage(labels.teamMember),
|
||||
[ROLES.teamViewOnly]: formatMessage(labels.viewOnly),
|
||||
};
|
||||
|
||||
return (
|
||||
@ -35,7 +37,14 @@ export function TeamMembersTable({
|
||||
allowEdit &&
|
||||
row?.role !== ROLES.teamOwner &&
|
||||
user?.id !== row?.id && (
|
||||
<TeamMemberRemoveButton teamId={teamId} userId={row?.user?.id} />
|
||||
<>
|
||||
<TeamMemberEditButton teamId={teamId} userId={row?.user?.id} role={row?.role} />
|
||||
<TeamMemberRemoveButton
|
||||
teamId={teamId}
|
||||
userId={row?.user?.id}
|
||||
userName={row?.user?.username}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}}
|
||||
|
@ -42,7 +42,7 @@ export const labels = defineMessages({
|
||||
owner: { id: 'label.owner', defaultMessage: 'Owner' },
|
||||
teamOwner: { id: 'label.team-owner', defaultMessage: 'Team owner' },
|
||||
teamMember: { id: 'label.team-member', defaultMessage: 'Team member' },
|
||||
teamGuest: { id: 'label.team-guest', defaultMessage: 'Team guest' },
|
||||
teamViewOnly: { id: 'label.team-view-only', defaultMessage: 'Team view only' },
|
||||
enableShareUrl: { id: 'label.enable-share-url', defaultMessage: 'Enable share URL' },
|
||||
data: { id: 'label.data', defaultMessage: 'Data' },
|
||||
trackingCode: { id: 'label.tracking-code', defaultMessage: 'Tracking code' },
|
||||
@ -56,6 +56,8 @@ export const labels = defineMessages({
|
||||
reset: { id: 'label.reset', defaultMessage: 'Reset' },
|
||||
addWebsite: { id: 'label.add-website', defaultMessage: 'Add website' },
|
||||
addMember: { id: 'label.add-member', defaultMessage: 'Add member' },
|
||||
editMember: { id: 'label.edit-member', defaultMessage: 'Edit member' },
|
||||
removeMember: { id: 'label.remove-member', defaultMessage: 'Remove member' },
|
||||
addDescription: { id: 'label.add-description', defaultMessage: 'Add description' },
|
||||
changePassword: { id: 'label.change-password', defaultMessage: 'Change password' },
|
||||
currentPassword: { id: 'label.current-password', defaultMessage: 'Current password' },
|
||||
@ -109,6 +111,7 @@ export const labels = defineMessages({
|
||||
allTime: { id: 'label.all-time', defaultMessage: 'All time' },
|
||||
customRange: { id: 'label.custom-range', defaultMessage: 'Custom range' },
|
||||
selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' },
|
||||
selectRole: { id: 'label.select-role', defaultMessage: 'Select role' },
|
||||
selectDate: { id: 'label.select-date', defaultMessage: 'Select date' },
|
||||
all: { id: 'label.all', defaultMessage: 'All' },
|
||||
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
||||
@ -220,6 +223,10 @@ export const messages = defineMessages({
|
||||
id: 'message.confirm-delete',
|
||||
defaultMessage: 'Are you sure you want to delete {target}?',
|
||||
},
|
||||
confirmRemove: {
|
||||
id: 'message.confirm-remove',
|
||||
defaultMessage: 'Are you sure you want to remove {target}?',
|
||||
},
|
||||
confirmLeave: {
|
||||
id: 'message.confirm-leave',
|
||||
defaultMessage: 'Are you sure you want to leave {target}?',
|
||||
|
@ -125,7 +125,7 @@ export const ROLES = {
|
||||
viewOnly: 'view-only',
|
||||
teamOwner: 'team-owner',
|
||||
teamMember: 'team-member',
|
||||
teamGuest: 'team-guest',
|
||||
teamViewOnly: 'team-view-only',
|
||||
} as const;
|
||||
|
||||
export const PERMISSIONS = {
|
||||
@ -159,7 +159,7 @@ export const ROLE_PERMISSIONS = {
|
||||
PERMISSIONS.websiteUpdate,
|
||||
PERMISSIONS.websiteDelete,
|
||||
],
|
||||
[ROLES.teamGuest]: [],
|
||||
[ROLES.teamViewOnly]: [],
|
||||
} as const;
|
||||
|
||||
export const THEME_COLORS = {
|
||||
|
@ -23,7 +23,7 @@ const schema = {
|
||||
POST: yup.object().shape({
|
||||
role: yup
|
||||
.string()
|
||||
.matches(/team-member|team-guest/i)
|
||||
.matches(/team-member|team-view-only/i)
|
||||
.required(),
|
||||
}),
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user