mirror of
https://github.com/suitenumerique/people.git
synced 2026-08-17 21:25:52 +02:00
✨(front) delete invitations mails domains access
add delete button for delete invitations to mails domains access
This commit is contained in:
@@ -10,6 +10,7 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(front) delete invitations mails domains access
|
||||
- ✨(front) add show invitations mails domains access #1040
|
||||
- ✨(invitations) can delete domain invitations
|
||||
|
||||
|
||||
@@ -3,4 +3,5 @@ export * from './useInvitationMailDomainAccesses';
|
||||
export * from './useUpdateMailDomainAccess';
|
||||
export * from './useCreateMailDomainAccess';
|
||||
export * from './useDeleteMailDomainAccess';
|
||||
export * from './useDeleteMailDomainInvitation';
|
||||
export * from './useCreateInvitation';
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
UseMutationOptions,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import {
|
||||
KEY_LIST_MAIL_DOMAIN,
|
||||
KEY_MAIL_DOMAIN,
|
||||
} from '@/features/mail-domains/domains';
|
||||
|
||||
import { KEY_LIST_INVITATION_DOMAIN_ACCESSES } from './useInvitationMailDomainAccesses';
|
||||
|
||||
interface DeleteMailDomainInvitationProps {
|
||||
slug: string;
|
||||
invitationId: string;
|
||||
}
|
||||
|
||||
export const deleteMailDomainInvitation = async ({
|
||||
slug,
|
||||
invitationId,
|
||||
}: DeleteMailDomainInvitationProps): Promise<void> => {
|
||||
const response = await fetchAPI(
|
||||
`mail-domains/${slug}/invitations/${invitationId}/`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIError(
|
||||
'Failed to delete the invitation',
|
||||
await errorCauses(response),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type UseDeleteMailDomainInvitationOptions = UseMutationOptions<
|
||||
void,
|
||||
APIError,
|
||||
DeleteMailDomainInvitationProps
|
||||
>;
|
||||
|
||||
export const useDeleteMailDomainInvitation = (
|
||||
options?: UseDeleteMailDomainInvitationOptions,
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
onSuccess: optionsOnSuccess,
|
||||
onError: optionsOnError,
|
||||
...restOptions
|
||||
} = options || {};
|
||||
return useMutation<void, APIError, DeleteMailDomainInvitationProps>({
|
||||
mutationFn: deleteMailDomainInvitation,
|
||||
...restOptions,
|
||||
onSuccess: (data, variables, context) => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_LIST_INVITATION_DOMAIN_ACCESSES],
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_MAIL_DOMAIN],
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_LIST_MAIL_DOMAIN],
|
||||
});
|
||||
if (optionsOnSuccess) {
|
||||
(
|
||||
optionsOnSuccess as unknown as (
|
||||
data: void,
|
||||
variables: DeleteMailDomainInvitationProps,
|
||||
context: unknown,
|
||||
) => void
|
||||
)(data, variables, context);
|
||||
}
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
if (optionsOnError) {
|
||||
(
|
||||
optionsOnError as unknown as (
|
||||
error: APIError,
|
||||
variables: DeleteMailDomainInvitationProps,
|
||||
context: unknown,
|
||||
) => void
|
||||
)(error, variables, context);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
+14
-10
@@ -12,6 +12,7 @@ import { PAGE_SIZE } from '../conf';
|
||||
import { Access } from '../types';
|
||||
|
||||
import { AccessAction } from './AccessAction';
|
||||
import { InvitationAction } from './InvitationAction';
|
||||
|
||||
interface AccessesListProps {
|
||||
mailDomain: MailDomain;
|
||||
@@ -165,20 +166,23 @@ export const AccessesList = ({
|
||||
email={access.user.email}
|
||||
showEmail
|
||||
/>
|
||||
<Text
|
||||
$size="small"
|
||||
$margin={{ left: '4px' }}
|
||||
$color={colorsTokens()['greyscale-500']}
|
||||
>
|
||||
{t('on pending')}
|
||||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<Box $direction="row" $gap="20px" $align="center">
|
||||
<Box
|
||||
$background={colorsTokens()['info-600']}
|
||||
$radius="4px"
|
||||
$padding={{ horizontal: 'base', vertical: '3px' }}
|
||||
>
|
||||
<Text $size="small" $color={colorsTokens()['info-100']}>
|
||||
{t('Pending')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box $direction="row" $align="center">
|
||||
<Text>{localizedRoles[access.role]}</Text>
|
||||
<InvitationAction
|
||||
mailDomain={mailDomain}
|
||||
access={access}
|
||||
currentRole={currentRole}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
Button,
|
||||
VariantType,
|
||||
useToastProvider,
|
||||
} from '@openfun/cunningham-react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { IconOptions, Text } from '@/components';
|
||||
|
||||
import { MailDomain, Role } from '../../domains/types';
|
||||
import { useDeleteMailDomainInvitation } from '../api';
|
||||
import { Access } from '../types';
|
||||
|
||||
interface InvitationActionProps {
|
||||
access: Access;
|
||||
currentRole: Role;
|
||||
mailDomain: MailDomain;
|
||||
}
|
||||
|
||||
export const InvitationAction = ({
|
||||
access,
|
||||
currentRole,
|
||||
mailDomain,
|
||||
}: InvitationActionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToastProvider();
|
||||
const [isDropOpen, setIsDropOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { mutate: deleteMailDomainInvitation } = useDeleteMailDomainInvitation({
|
||||
onSuccess: () => {
|
||||
toast(t('The invitation has been deleted'), VariantType.SUCCESS, {
|
||||
duration: 4000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(e.target as Node)
|
||||
) {
|
||||
setIsDropOpen(false);
|
||||
}
|
||||
};
|
||||
if (isDropOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [isDropOpen]);
|
||||
|
||||
if (currentRole === Role.VIEWER || !mailDomain.abilities.delete) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ position: 'relative', display: 'inline-block' }}
|
||||
ref={dropdownRef}
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsDropOpen((prev) => !prev)}
|
||||
aria-label={t('Open the invitation options modal')}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: 4,
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<IconOptions />
|
||||
</button>
|
||||
|
||||
{isDropOpen && (
|
||||
<div
|
||||
role="menu"
|
||||
tabIndex={0}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
background: 'white',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: 4,
|
||||
padding: '0.5rem',
|
||||
minWidth: '210px',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape' || e.key === 'Enter') {
|
||||
setIsDropOpen(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
aria-label={t('Delete this invitation')}
|
||||
onClick={() => {
|
||||
deleteMailDomainInvitation({
|
||||
slug: mailDomain.slug,
|
||||
invitationId: access.id,
|
||||
});
|
||||
setIsDropOpen(false);
|
||||
}}
|
||||
color="primary-text"
|
||||
size="small"
|
||||
fullWidth
|
||||
icon={
|
||||
<span className="material-icons" aria-hidden="true">
|
||||
delete
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Text $theme="primary">{t('Delete invitation')}</Text>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user