From c7e9190c9587295627c38c35be8f049cb98a3985 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Thu, 8 Feb 2024 13:01:39 -0800 Subject: [PATCH] add team member edit and remove confirmation --- .../[teamId]/members/TeamMemberEditButton.tsx | 49 ++++++++++++ .../[teamId]/members/TeamMemberEditForm.tsx | 78 +++++++++++++++++++ .../members/TeamMemberRemoveButton.tsx | 48 ++++++++---- .../[teamId]/members/TeamMembersTable.tsx | 11 ++- src/components/messages.ts | 9 ++- src/lib/constants.ts | 4 +- .../api/teams/[teamId]/users/[userId].ts | 2 +- 7 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditButton.tsx create mode 100644 src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditForm.tsx diff --git a/src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditButton.tsx b/src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditButton.tsx new file mode 100644 index 00000000..31186055 --- /dev/null +++ b/src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditButton.tsx @@ -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 ( + + + + {(close: () => void) => ( + + )} + + + ); +} + +export default TeamMemberEditButton; diff --git a/src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditForm.tsx b/src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditForm.tsx new file mode 100644 index 00000000..b54757e7 --- /dev/null +++ b/src/app/(main)/settings/teams/[teamId]/members/TeamMemberEditForm.tsx @@ -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 ( +
+ + + + {formatMessage(labels.teamMember)} + {formatMessage(labels.viewOnly)} + + + + + + {formatMessage(labels.save)} + + + +
+ ); +} + +export default UserAddForm; diff --git a/src/app/(main)/settings/teams/[teamId]/members/TeamMemberRemoveButton.tsx b/src/app/(main)/settings/teams/[teamId]/members/TeamMemberRemoveButton.tsx index bb944061..f19d857b 100644 --- a/src/app/(main)/settings/teams/[teamId]/members/TeamMemberRemoveButton.tsx +++ b/src/app/(main)/settings/teams/[teamId]/members/TeamMemberRemoveButton.tsx @@ -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 ( - handleRemoveTeamMember()} - disabled={disabled} - isLoading={isPending} - > - - - - {formatMessage(labels.remove)} - + + + + {(close: () => void) => ( + {userName} }} + /> + } + isLoading={isPending} + error={error} + onConfirm={handleConfirm.bind(null, close)} + onClose={close} + buttonLabel={formatMessage(labels.remove)} + /> + )} + + ); } diff --git a/src/app/(main)/settings/teams/[teamId]/members/TeamMembersTable.tsx b/src/app/(main)/settings/teams/[teamId]/members/TeamMembersTable.tsx index 0b60293a..2f3f75c2 100644 --- a/src/app/(main)/settings/teams/[teamId]/members/TeamMembersTable.tsx +++ b/src/app/(main)/settings/teams/[teamId]/members/TeamMembersTable.tsx @@ -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 && ( - + <> + + + ) ); }} diff --git a/src/components/messages.ts b/src/components/messages.ts index 19f60b46..0577f8c6 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -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}?', diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 1dc1c0d5..97535899 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -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 = { diff --git a/src/pages/api/teams/[teamId]/users/[userId].ts b/src/pages/api/teams/[teamId]/users/[userId].ts index 02e5eecd..4b52fe3e 100644 --- a/src/pages/api/teams/[teamId]/users/[userId].ts +++ b/src/pages/api/teams/[teamId]/users/[userId].ts @@ -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(), }), };