From 4dab4640745b0d6006616940083cff0e0275c349 Mon Sep 17 00:00:00 2001 From: Bartosz Dokurno Date: Mon, 3 Jun 2024 12:05:42 +0200 Subject: [PATCH] Add option to transfer organization ownership Fixes: #58 --- .../ConfirmMemberDeleteModal.tsx | 2 + .../ConfirmTransferModal.tsx | 80 +++++++++++++++++++ .../OrganizationMemberDropdown.tsx | 11 ++- .../TransferOwnershipOption.tsx | 45 +++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmTransferModal.tsx create mode 100644 apps/client/src/components/Organization/OrganizationMemberDropdown/TransferOwnershipOption.tsx diff --git a/apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmMemberDeleteModal.tsx b/apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmMemberDeleteModal.tsx index 7300186..3f3b6a4 100644 --- a/apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmMemberDeleteModal.tsx +++ b/apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmMemberDeleteModal.tsx @@ -12,6 +12,7 @@ import ModalDialog, { ModalDialogProps } from '../../Modal/ModalDialog'; import ModalHeader from '../../Modal/ModalHeader'; import ModalTitle from '../../Modal/ModalTitle'; import ModelFooter from '../../Modal/ModelFooter'; +import toast from 'react-hot-toast'; export interface ConfirmMemberDeleteModalProps extends ModalDialogProps { organization: OrganizationDto; @@ -38,6 +39,7 @@ function ConfirmMemberDeleteModal({ organization, rule, ...props }: ConfirmMembe .delete(`/api/security`, { data: dto }) .then(() => { queryClient.invalidateQueries(['security', organization.id]); + toast.success(`Removed ${user?.username} from organization`); if (!props.handleClose) return; props.handleClose(); }) diff --git a/apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmTransferModal.tsx b/apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmTransferModal.tsx new file mode 100644 index 0000000..6bf82d9 --- /dev/null +++ b/apps/client/src/components/Organization/OrganizationMemberDropdown/ConfirmTransferModal.tsx @@ -0,0 +1,80 @@ +import axios from 'axios'; +import { useState } from 'react'; +import { useQueryClient } from 'react-query'; +import { + IDeleteSecurityRuleDto, + ITransferOrganizationDto, + OrganizationDto, + SecurityRuleDto, +} from 'shared-types'; +import useUserData from '../../../hooks/useUserData'; +import { Utils } from '../../../utils/utils'; +import Button from '../../Button'; +import Alert from '../../Helpers/Alert'; +import ModalBody from '../../Modal/ModalBody'; +import ModalCloseButton from '../../Modal/ModalCloseButton'; +import ModalDialog, { ModalDialogProps } from '../../Modal/ModalDialog'; +import ModalHeader from '../../Modal/ModalHeader'; +import ModalTitle from '../../Modal/ModalTitle'; +import ModelFooter from '../../Modal/ModelFooter'; +import { BsArrowRight } from 'react-icons/bs'; +import IconButton from '../../IconButton'; +import toast from 'react-hot-toast'; + +export interface ConfirmTransferModalProps extends ModalDialogProps { + organization: OrganizationDto; + rule: SecurityRuleDto; +} + +function ConfirmTransferModal({ organization, rule, ...props }: ConfirmTransferModalProps) { + const { user } = useUserData(rule.user); + const queryClient = useQueryClient(); + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + function handleClick() { + setLoading(true); + setError(null); + + const dto: ITransferOrganizationDto = { user: rule.user }; + axios + .post(`/api/security/${organization.id}/transfer`, dto) + .then(() => { + queryClient.invalidateQueries(['security', organization.id]); + toast.success(`Transfered organization to ${user?.username}`); + if (!props.handleClose) return; + props.handleClose(); + }) + .catch((err) => setError(Utils.requestErrorToString(err))) + .finally(() => setLoading(false)); + } + + return ( + + + Transfer organization ownership + + + + {error && {error}} + You are about to transfer ownership of this organization ({organization.name}) to{' '} + {user?.username || '...'}. You will immediately lose owner access, this action is not + reversible. Are you sure? + + + + + Transfer + + + + + ); +} +export default ConfirmTransferModal; diff --git a/apps/client/src/components/Organization/OrganizationMemberDropdown/OrganizationMemberDropdown.tsx b/apps/client/src/components/Organization/OrganizationMemberDropdown/OrganizationMemberDropdown.tsx index cad5529..f549c7d 100644 --- a/apps/client/src/components/Organization/OrganizationMemberDropdown/OrganizationMemberDropdown.tsx +++ b/apps/client/src/components/Organization/OrganizationMemberDropdown/OrganizationMemberDropdown.tsx @@ -6,6 +6,7 @@ import DropdownItem from '../../Dropdown/DropdownItem'; import DropdownMenu from '../../Dropdown/DropdownMenu'; import DropdownToggle from '../../Dropdown/DropdownToggle'; import RemoveMemberOption from './RemoveMemberOption'; +import TransferOwnershipOption from './TransferOwnershipOption'; export interface OrganizationMemberDropdownProps { organization: OrganizationDto; @@ -28,12 +29,10 @@ function OrganizationMemberDropdown({ /> - - Transfer Ownership - + { + organization: OrganizationDto; + rule: SecurityRuleDto; +} + +function TransferOwnershipOption({ rule, organization, ...props }: TransferOwnershipOptionProps) { + const [open, setOpen] = useState(false); + + const { role } = useUserRole(organization.id); + const isOwner = role == OrganizationSecurityRole.OWNER; + + return ( + <> + {createPortal( + setOpen(false)} + organization={organization} + rule={rule} + />, + document.body, + )} + setOpen(true)} + > + Transfer ownership + + + ); +} +export default TransferOwnershipOption;