From 8bae9311e2e258ca02b6cc0fff6445a0fdf7733a Mon Sep 17 00:00:00 2001 From: Tiago Noronha Date: Thu, 21 Nov 2024 10:26:43 +0000 Subject: [PATCH] Implement Agency ownership transfer --- .../data/team/use-transfer-ownership.ts | 44 +++++++++++++++++++ .../team/hooks/use-handle-member-action.ts | 32 ++++++++++++++ .../team/primary/team-list/columns.tsx | 24 ++++++++++ 3 files changed, 100 insertions(+) create mode 100644 client/a8c-for-agencies/data/team/use-transfer-ownership.ts diff --git a/client/a8c-for-agencies/data/team/use-transfer-ownership.ts b/client/a8c-for-agencies/data/team/use-transfer-ownership.ts new file mode 100644 index 00000000000000..2314c3a4ff86ef --- /dev/null +++ b/client/a8c-for-agencies/data/team/use-transfer-ownership.ts @@ -0,0 +1,44 @@ +import { useMutation, UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; +import wpcom from 'calypso/lib/wp'; +import { useSelector } from 'calypso/state'; +import { getActiveAgencyId } from 'calypso/state/a8c-for-agencies/agency/selectors'; + +interface APIError { + status: number; + code: string | null; + message: string; +} + +export interface Params { + id: number; +} + +interface APIResponse { + success: boolean; +} + +function transferOwnershipMutation( params: Params, agencyId?: number ): Promise< APIResponse > { + if ( ! agencyId ) { + throw new Error( 'Agency ID is required to transfer ownership' ); + } + + return wpcom.req.post( { + apiNamespace: 'wpcom/v2', + path: `/agency/${ agencyId }/transfer-ownership`, + method: 'PUT', + body: { + new_owner_id: params.id, + }, + } ); +} + +export default function useTransferOwnershipMutation< TContext = unknown >( + options?: UseMutationOptions< APIResponse, APIError, Params, TContext > +): UseMutationResult< APIResponse, APIError, Params, TContext > { + const agencyId = useSelector( getActiveAgencyId ); + + return useMutation< APIResponse, APIError, Params, TContext >( { + ...options, + mutationFn: ( args ) => transferOwnershipMutation( args, agencyId ), + } ); +} diff --git a/client/a8c-for-agencies/sections/team/hooks/use-handle-member-action.ts b/client/a8c-for-agencies/sections/team/hooks/use-handle-member-action.ts index 3145a8f23c439c..2e0c18a16fa1b6 100644 --- a/client/a8c-for-agencies/sections/team/hooks/use-handle-member-action.ts +++ b/client/a8c-for-agencies/sections/team/hooks/use-handle-member-action.ts @@ -4,6 +4,7 @@ import { useCallback } from 'react'; import useCancelMemberInviteMutation from 'calypso/a8c-for-agencies/data/team/use-cancel-member-invite'; import useRemoveMemberMutation from 'calypso/a8c-for-agencies/data/team/use-remove-member'; import useResendMemberInviteMutation from 'calypso/a8c-for-agencies/data/team/use-resend-member-invite'; +import useTransferOwnershipMutation from 'calypso/a8c-for-agencies/data/team/use-transfer-ownership'; import { useDispatch, useSelector } from 'calypso/state'; import { errorNotice, successNotice } from 'calypso/state/notices/actions'; import { TeamMember } from '../types'; @@ -22,6 +23,8 @@ export default function useHandleMemberAction( { onRefetchList }: Props ) { const { mutate: removeMember } = useRemoveMemberMutation(); + const { mutate: transferOwnership } = useTransferOwnershipMutation(); + const currentUser = useSelector( getCurrentUser ); return useCallback( @@ -114,6 +117,34 @@ export default function useHandleMemberAction( { onRefetchList }: Props ) { } ); } + + if ( action === 'transfer-ownership' ) { + transferOwnership( + { id: item.id }, + { + onSuccess: () => { + dispatch( + successNotice( translate( 'Ownership has been successfully transferred.' ), { + id: 'transfer-ownership-success', + duration: 5000, + } ) + ); + onRefetchList?.(); + callback?.(); + }, + + onError: ( error ) => { + dispatch( + errorNotice( error.message, { + id: 'transfer-ownership-error', + duration: 5000, + } ) + ); + callback?.(); + }, + } + ); + } }, [ cancelMemberInvite, @@ -123,6 +154,7 @@ export default function useHandleMemberAction( { onRefetchList }: Props ) { removeMember, resendMemberInvite, translate, + transferOwnership, ] ); } diff --git a/client/a8c-for-agencies/sections/team/primary/team-list/columns.tsx b/client/a8c-for-agencies/sections/team/primary/team-list/columns.tsx index d5d70311884fba..2e9bf92a2126d1 100644 --- a/client/a8c-for-agencies/sections/team/primary/team-list/columns.tsx +++ b/client/a8c-for-agencies/sections/team/primary/team-list/columns.tsx @@ -183,6 +183,29 @@ export const ActionColumn = ( { label: translate( 'Send password reset' ), isEnabled: false, // FIXME: Implement this action }, + { + name: 'transfer-ownership', + label: translate( 'Transfer ownership' ), + isEnabled: member.status === 'active' && currentUser.email !== member.email, + confirmationDialog: { + title: translate( 'Transfer agency ownership' ), + children: translate( + 'Are you sure you want to transfer ownership of %(agencyName)s to {{b}}%(memberName)s{{/b}}? {{br/}}This action cannot be undone and you will become a regular team member.', + { + args: { + agencyName: agency?.name ?? '', + memberName: member.displayName ?? member.email, + }, + components: { + b: , + br:
, + }, + comment: '%(agencyName)s is the agency name, %(memberName)s is the member name', + } + ), + ctaLabel: translate( 'Transfer ownership' ), + }, + }, { name: 'delete-user', label: isSelfAction ? translate( 'Leave agency' ) : translate( 'Remove team member' ), @@ -226,6 +249,7 @@ export const ActionColumn = ( { canRemove, isSelfAction, agency?.name, + currentUser.email, ] ); const activeActions = actions.filter( ( { isEnabled } ) => isEnabled );