✨(frontend) add new access role to domain
add new access role to domain first commit
This commit is contained in:
committed by
Sabrina Demagny
parent
ea1f06f6cc
commit
67d9b6462f
@@ -10,6 +10,7 @@ and this project adheres to
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- ✨(frontend) feature modal add new access role to domain
|
||||||
- ✨(api) allow invitations for domain management #708
|
- ✨(api) allow invitations for domain management #708
|
||||||
|
|
||||||
## [1.13.1] - 2025-03-04
|
## [1.13.1] - 2025-03-04
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -156,6 +156,10 @@ lint-pylint: ## lint back-end python sources with pylint only on changed files f
|
|||||||
bin/pylint --diff-only=origin/main
|
bin/pylint --diff-only=origin/main
|
||||||
.PHONY: lint-pylint
|
.PHONY: lint-pylint
|
||||||
|
|
||||||
|
lint-front:
|
||||||
|
cd $(PATH_FRONT) && yarn lint
|
||||||
|
.PHONY: lint-front
|
||||||
|
|
||||||
test: ## run project tests
|
test: ## run project tests
|
||||||
@$(MAKE) test-back-parallel
|
@$(MAKE) test-back-parallel
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|||||||
@@ -327,6 +327,10 @@ input:-webkit-autofill:focus {
|
|||||||
outline: var(--c--theme--colors--primary-600) solid 2px;
|
outline: var(--c--theme--colors--primary-600) solid 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c__radio input::before {
|
||||||
|
box-shadow: inset 1em 1em var(--c--theme--colors--primary-600);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button
|
* Button
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './useMailDomainAccesses';
|
export * from './useMailDomainAccesses';
|
||||||
export * from './useUpdateMailDomainAccess';
|
export * from './useUpdateMailDomainAccess';
|
||||||
|
export * from './useCreateMailDomainAccess';
|
||||||
export * from './useDeleteMailDomainAccess';
|
export * from './useDeleteMailDomainAccess';
|
||||||
|
export * from './useCreateInvitation';
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
|
import { User } from '@/core/auth';
|
||||||
|
import { Invitation, OptionType } from '@/features/teams/member-add/types';
|
||||||
|
|
||||||
|
import { MailDomain, Role } from '../../domains';
|
||||||
|
|
||||||
|
interface CreateInvitationParams {
|
||||||
|
email: User['email'];
|
||||||
|
role: Role;
|
||||||
|
mailDomainSlug: MailDomain['slug'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createInvitation = async ({
|
||||||
|
email,
|
||||||
|
role,
|
||||||
|
mailDomainSlug,
|
||||||
|
}: CreateInvitationParams): Promise<Invitation> => {
|
||||||
|
const response = await fetchAPI(
|
||||||
|
`mail-domains/${mailDomainSlug}/invitations/`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
email,
|
||||||
|
role,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new APIError(
|
||||||
|
`Failed to create the invitation for ${email}`,
|
||||||
|
await errorCauses(response, {
|
||||||
|
value: email,
|
||||||
|
type: OptionType.INVITATION,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<Invitation>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useCreateInvitation() {
|
||||||
|
return useMutation<Invitation, APIError, CreateInvitationParams>({
|
||||||
|
mutationFn: createInvitation,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
UseMutationOptions,
|
||||||
|
useMutation,
|
||||||
|
useQueryClient,
|
||||||
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
|
import { KEY_MAIL_DOMAIN, Role } from '@/features/mail-domains/domains';
|
||||||
|
|
||||||
|
import { Access } from '../types';
|
||||||
|
|
||||||
|
import { KEY_LIST_MAIL_DOMAIN_ACCESSES } from './useMailDomainAccesses';
|
||||||
|
|
||||||
|
interface CreateMailDomainAccessProps {
|
||||||
|
slug: string;
|
||||||
|
user: string;
|
||||||
|
role: Role;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createMailDomainAccess = async ({
|
||||||
|
slug,
|
||||||
|
user,
|
||||||
|
role,
|
||||||
|
}: CreateMailDomainAccessProps): Promise<Access> => {
|
||||||
|
const response = await fetchAPI(`mail-domains/${slug}/accesses/`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ user, role }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new APIError('Failed to create role', await errorCauses(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<Access>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseCreateMailDomainAccessOptions = UseMutationOptions<
|
||||||
|
Access,
|
||||||
|
APIError,
|
||||||
|
CreateMailDomainAccessProps
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useCreateMailDomainAccess = (
|
||||||
|
options?: UseCreateMailDomainAccessOptions,
|
||||||
|
) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<Access, APIError, CreateMailDomainAccessProps>({
|
||||||
|
mutationFn: createMailDomainAccess,
|
||||||
|
...options,
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [KEY_LIST_MAIL_DOMAIN_ACCESSES],
|
||||||
|
});
|
||||||
|
void queryClient.invalidateQueries({ queryKey: [KEY_MAIL_DOMAIN] });
|
||||||
|
options?.onSuccess?.(data, variables, context);
|
||||||
|
},
|
||||||
|
onError: (error, variables, context) => {
|
||||||
|
options?.onError?.(error, variables, context);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
|
import { User } from '@/core/auth';
|
||||||
|
import { MailDomain } from '@/features/mail-domains/domains/types';
|
||||||
|
|
||||||
|
export type UsersParams = {
|
||||||
|
query: string;
|
||||||
|
mailDomain: MailDomain['slug'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type UsersResponse = User[];
|
||||||
|
|
||||||
|
export const getUsers = async ({
|
||||||
|
query,
|
||||||
|
mailDomain,
|
||||||
|
}: UsersParams): Promise<UsersResponse> => {
|
||||||
|
const response = await fetchAPI(
|
||||||
|
`mail-domains/${mailDomain}/accesses/users/?q=${query}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new APIError('Failed to get the users', await errorCauses(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = (await response.json()) as User[];
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KEY_LIST_USER = 'users';
|
||||||
|
|
||||||
|
export function useUsers(
|
||||||
|
param: UsersParams,
|
||||||
|
queryConfig?: UseQueryOptions<UsersResponse, APIError, UsersResponse>,
|
||||||
|
) {
|
||||||
|
return useQuery<UsersResponse, APIError, UsersResponse>({
|
||||||
|
queryKey: [KEY_LIST_USER, param],
|
||||||
|
queryFn: () => getUsers(param),
|
||||||
|
...queryConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,17 +1,56 @@
|
|||||||
import React from 'react';
|
import { Button } from '@openfun/cunningham-react';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { Box } from '@/components';
|
||||||
import { AccessesGrid } from '@/features/mail-domains/access-management/components/AccessesGrid';
|
import { AccessesGrid } from '@/features/mail-domains/access-management/components/AccessesGrid';
|
||||||
|
|
||||||
import { MailDomain, Role } from '../../domains';
|
import { MailDomain, Role } from '../../domains';
|
||||||
|
|
||||||
|
import { ModalCreateAccess } from './ModalCreateAccess';
|
||||||
|
|
||||||
export const AccessesContent = ({
|
export const AccessesContent = ({
|
||||||
mailDomain,
|
mailDomain,
|
||||||
currentRole,
|
currentRole,
|
||||||
}: {
|
}: {
|
||||||
mailDomain: MailDomain;
|
mailDomain: MailDomain;
|
||||||
currentRole: Role;
|
currentRole: Role;
|
||||||
}) => (
|
}) => {
|
||||||
<>
|
const { t } = useTranslation();
|
||||||
<AccessesGrid mailDomain={mailDomain} currentRole={currentRole} />
|
|
||||||
</>
|
const [isModalAccessOpen, setIsModalAccessOpen] = useState(false);
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
$direction="row"
|
||||||
|
$justify="flex-end"
|
||||||
|
$margin={{ bottom: 'small' }}
|
||||||
|
$align="center"
|
||||||
|
>
|
||||||
|
<Box $display="flex" $direction="row">
|
||||||
|
{mailDomain?.abilities.post && (
|
||||||
|
<Button
|
||||||
|
aria-label={t('Add a new access in {{name}} domain', {
|
||||||
|
name: mailDomain?.name,
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
setIsModalAccessOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Add a new access')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<AccessesGrid mailDomain={mailDomain} currentRole={currentRole} />
|
||||||
|
{isModalAccessOpen && mailDomain && (
|
||||||
|
<ModalCreateAccess
|
||||||
|
mailDomain={mailDomain}
|
||||||
|
currentRole={currentRole}
|
||||||
|
onClose={() => setIsModalAccessOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ModalSize,
|
||||||
|
VariantType,
|
||||||
|
useToastProvider,
|
||||||
|
} from '@openfun/cunningham-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { APIError } from '@/api';
|
||||||
|
import { Box, Text } from '@/components';
|
||||||
|
import { Modal } from '@/components/Modal';
|
||||||
|
import { useCreateMailDomainAccess } from '@/features/mail-domains/access-management';
|
||||||
|
import {
|
||||||
|
OptionSelect,
|
||||||
|
OptionType,
|
||||||
|
isOptionNewMember,
|
||||||
|
} from '@/features/teams/member-add/types';
|
||||||
|
|
||||||
|
import { MailDomain, Role } from '../../domains';
|
||||||
|
import { useCreateInvitation } from '../api';
|
||||||
|
|
||||||
|
import { ChooseRole } from './ChooseRole';
|
||||||
|
import { OptionsSelect, SearchMembers } from './SearchMembers';
|
||||||
|
|
||||||
|
interface ModalCreateAccessProps {
|
||||||
|
mailDomain: MailDomain;
|
||||||
|
currentRole: Role;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIErrorMember = APIError<{
|
||||||
|
value: string;
|
||||||
|
type: OptionType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const ModalCreateAccess = ({
|
||||||
|
mailDomain,
|
||||||
|
currentRole,
|
||||||
|
onClose,
|
||||||
|
}: ModalCreateAccessProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToastProvider();
|
||||||
|
const [selectedMembers, setSelectedMembers] = useState<OptionsSelect>([]);
|
||||||
|
const [role, setRole] = useState<Role>(Role.VIEWER);
|
||||||
|
|
||||||
|
const createInvitation = useCreateInvitation();
|
||||||
|
const { mutateAsync: createMailDomainAccess } = useCreateMailDomainAccess();
|
||||||
|
|
||||||
|
const onSuccess = (option: OptionSelect) => {
|
||||||
|
const message = !isOptionNewMember(option)
|
||||||
|
? t('Invitation sent to {{email}}', {
|
||||||
|
email: option.value.email,
|
||||||
|
})
|
||||||
|
: t('Access added to {{name}}', {
|
||||||
|
name: option.value.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast(message, VariantType.SUCCESS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = (dataError: APIErrorMember['data']) => {
|
||||||
|
const messageError =
|
||||||
|
dataError?.type === OptionType.INVITATION
|
||||||
|
? t('Failed to create the invitation')
|
||||||
|
: t('Failed to add access');
|
||||||
|
toast(messageError, VariantType.ERROR);
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchActions = (selectedMembers: OptionsSelect) =>
|
||||||
|
selectedMembers.map(async (selectedMember) => {
|
||||||
|
switch (selectedMember.type) {
|
||||||
|
case OptionType.INVITATION:
|
||||||
|
await createInvitation.mutateAsync({
|
||||||
|
email: selectedMember.value.email,
|
||||||
|
mailDomainSlug: mailDomain.slug,
|
||||||
|
role,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
await createMailDomainAccess({
|
||||||
|
slug: mailDomain.slug,
|
||||||
|
user: selectedMember.value.id,
|
||||||
|
role,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedMember;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleValidate = async () => {
|
||||||
|
const settledPromises = await Promise.allSettled(
|
||||||
|
switchActions(selectedMembers),
|
||||||
|
);
|
||||||
|
|
||||||
|
settledPromises.forEach((settledPromise) => {
|
||||||
|
switch (settledPromise.status) {
|
||||||
|
case 'rejected':
|
||||||
|
onError((settledPromise.reason as APIErrorMember).data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fulfilled':
|
||||||
|
onSuccess(settledPromise.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen
|
||||||
|
leftActions={
|
||||||
|
<Button color="secondary" fullWidth onClick={onClose}>
|
||||||
|
{t('Cancel')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
onClose={onClose}
|
||||||
|
closeOnClickOutside
|
||||||
|
hideCloseButton
|
||||||
|
rightActions={
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
disabled={!selectedMembers.length}
|
||||||
|
onClick={() => void handleValidate()}
|
||||||
|
>
|
||||||
|
{t('Add to domain')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size={ModalSize.MEDIUM}
|
||||||
|
title={
|
||||||
|
<Box $align="center" $gap="1rem">
|
||||||
|
<Text $size="h3" $margin="none">
|
||||||
|
{t('Add a new access')}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Box $margin={{ bottom: 'xl', top: 'large' }}>
|
||||||
|
<SearchMembers
|
||||||
|
mailDomain={mailDomain}
|
||||||
|
setSelectedMembers={setSelectedMembers}
|
||||||
|
selectedMembers={selectedMembers}
|
||||||
|
/>
|
||||||
|
{selectedMembers.length > 0 && (
|
||||||
|
<Box $margin={{ top: 'small' }}>
|
||||||
|
<Text as="h4" $textAlign="left" $margin={{ bottom: 'tiny' }}>
|
||||||
|
{t('Choose a role')}
|
||||||
|
</Text>
|
||||||
|
<ChooseRole
|
||||||
|
roleAccess={currentRole}
|
||||||
|
disabled={false}
|
||||||
|
availableRoles={[Role.VIEWER, Role.ADMIN, Role.OWNER]}
|
||||||
|
currentRole={currentRole}
|
||||||
|
setRole={setRole}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Options } from 'react-select';
|
||||||
|
import AsyncSelect from 'react-select/async';
|
||||||
|
|
||||||
|
import { MailDomain } from '@/features/mail-domains/domains/types';
|
||||||
|
import { OptionSelect, OptionType } from '@/features/teams/member-add/types';
|
||||||
|
import { isValidEmail } from '@/utils';
|
||||||
|
|
||||||
|
import { useUsers } from '../api/useUsers';
|
||||||
|
|
||||||
|
export type OptionsSelect = Options<OptionSelect>;
|
||||||
|
|
||||||
|
interface SearchMembersProps {
|
||||||
|
mailDomain: MailDomain;
|
||||||
|
selectedMembers: OptionsSelect;
|
||||||
|
setSelectedMembers: (value: OptionsSelect) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchMembers = ({
|
||||||
|
mailDomain,
|
||||||
|
selectedMembers,
|
||||||
|
setSelectedMembers,
|
||||||
|
disabled,
|
||||||
|
}: SearchMembersProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [input, setInput] = useState('');
|
||||||
|
const [userQuery, setUserQuery] = useState('');
|
||||||
|
const resolveOptionsRef = useRef<((value: OptionsSelect) => void) | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const { data } = useUsers({
|
||||||
|
query: userQuery,
|
||||||
|
mailDomain: mailDomain.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = data;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!resolveOptionsRef.current || !options) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionsFiltered = options.filter(
|
||||||
|
(user) =>
|
||||||
|
!selectedMembers?.find(
|
||||||
|
(selectedUser) => selectedUser.value.email === user.email,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let users: OptionsSelect = optionsFiltered.map((user) => ({
|
||||||
|
value: user,
|
||||||
|
label: user.name || user.email,
|
||||||
|
type: OptionType.NEW_MEMBER,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (userQuery && isValidEmail(userQuery)) {
|
||||||
|
const isFoundUser = !!optionsFiltered.find(
|
||||||
|
(user) => user.email === userQuery,
|
||||||
|
);
|
||||||
|
const isFoundEmail = !!selectedMembers.find(
|
||||||
|
(selectedMember) => selectedMember.value.email === userQuery,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isFoundUser && !isFoundEmail) {
|
||||||
|
users = [
|
||||||
|
{
|
||||||
|
value: { email: userQuery },
|
||||||
|
label: userQuery,
|
||||||
|
type: OptionType.INVITATION,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveOptionsRef.current(users);
|
||||||
|
resolveOptionsRef.current = null;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [options, selectedMembers]);
|
||||||
|
|
||||||
|
const loadOptions = (): Promise<OptionsSelect> => {
|
||||||
|
return new Promise<OptionsSelect>((resolve) => {
|
||||||
|
resolveOptionsRef.current = resolve;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const onInputChangeHandle = useCallback((newValue: string) => {
|
||||||
|
setInput(newValue);
|
||||||
|
if (timeout.current) {
|
||||||
|
clearTimeout(timeout.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout.current = setTimeout(() => {
|
||||||
|
setUserQuery(newValue);
|
||||||
|
}, 1000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncSelect
|
||||||
|
isDisabled={disabled}
|
||||||
|
aria-label={t('Find a member to add to the domain')}
|
||||||
|
isMulti
|
||||||
|
loadOptions={loadOptions}
|
||||||
|
defaultOptions={[]}
|
||||||
|
onInputChange={onInputChangeHandle}
|
||||||
|
inputValue={input}
|
||||||
|
placeholder={t(
|
||||||
|
'Search for members to assign them a role (name or email)',
|
||||||
|
{},
|
||||||
|
)}
|
||||||
|
noOptionsMessage={() =>
|
||||||
|
t('Invite new members with roles', { name: mailDomain.name })
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
setInput('');
|
||||||
|
setUserQuery('');
|
||||||
|
setSelectedMembers(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useToastProvider } from '@openfun/cunningham-react';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { useCreateMailDomainAccess } from '@/features/mail-domains/access-management';
|
||||||
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
|
import { MailDomain, Role } from '../../../domains';
|
||||||
|
import { ModalCreateAccess } from '../ModalCreateAccess';
|
||||||
|
|
||||||
|
const domain: MailDomain = {
|
||||||
|
id: '897-9879-986789-89798-897',
|
||||||
|
name: 'Domain test',
|
||||||
|
created_at: '121212',
|
||||||
|
updated_at: '121212',
|
||||||
|
slug: 'test-domain',
|
||||||
|
status: 'pending',
|
||||||
|
support_email: 'sfs@test-domain.fr',
|
||||||
|
abilities: {
|
||||||
|
get: true,
|
||||||
|
patch: true,
|
||||||
|
put: true,
|
||||||
|
post: true,
|
||||||
|
delete: true,
|
||||||
|
manage_accesses: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('@openfun/cunningham-react', () => ({
|
||||||
|
...jest.requireActual('@openfun/cunningham-react'),
|
||||||
|
useToastProvider: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../api', () => ({
|
||||||
|
useCreateInvitation: jest.fn(() => ({ mutateAsync: jest.fn() })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@/features/mail-domains/access-management', () => ({
|
||||||
|
useCreateMailDomainAccess: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ModalCreateAccess', () => {
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
const mockToast = jest.fn();
|
||||||
|
const mockCreateMailDomainAccess = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
fetchMock.restore();
|
||||||
|
(useToastProvider as jest.Mock).mockReturnValue({ toast: mockToast });
|
||||||
|
|
||||||
|
(useCreateMailDomainAccess as jest.Mock).mockReturnValue({
|
||||||
|
mutateAsync: mockCreateMailDomainAccess,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderModalCreateAccess = () => {
|
||||||
|
return render(
|
||||||
|
<ModalCreateAccess
|
||||||
|
mailDomain={domain}
|
||||||
|
currentRole={Role.ADMIN}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
/>,
|
||||||
|
{ wrapper: AppWrapper },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('renders the modal with all elements', () => {
|
||||||
|
renderModalCreateAccess();
|
||||||
|
expect(screen.getByText('Add a new access')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /Cancel/i })).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: /Add to domain/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onClose when Cancel is clicked', async () => {
|
||||||
|
renderModalCreateAccess();
|
||||||
|
const cancelButton = screen.getByRole('button', { name: /Cancel/i });
|
||||||
|
await userEvent.click(cancelButton);
|
||||||
|
await waitFor(() => expect(mockOnClose).toHaveBeenCalledTimes(1), {
|
||||||
|
timeout: 3000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"0 group to display.": "0 groupe à afficher.",
|
"0 group to display.": "0 groupe à afficher.",
|
||||||
"Access icon": "Icône d'accès",
|
"Access icon": "Icône d'accès",
|
||||||
"Access management": "Gestion des rôles",
|
"Access management": "Gestion des rôles",
|
||||||
|
"Access added to {{name}}": "Accès accordés à {{name}}",
|
||||||
"Accesses list card": "Carte de la liste des accès",
|
"Accesses list card": "Carte de la liste des accès",
|
||||||
"Accessibility statement": "Déclaration d'accessibilité",
|
"Accessibility statement": "Déclaration d'accessibilité",
|
||||||
"Accessibility: non-compliant": "Accessibilité : non conforme",
|
"Accessibility: non-compliant": "Accessibilité : non conforme",
|
||||||
@@ -29,11 +30,13 @@
|
|||||||
"Actions required detail": "Détail des actions requises",
|
"Actions required detail": "Détail des actions requises",
|
||||||
"Add a mail domain": "Ajouter un nom de domaine",
|
"Add a mail domain": "Ajouter un nom de domaine",
|
||||||
"Add a member": "Ajouter un membre",
|
"Add a member": "Ajouter un membre",
|
||||||
|
"Add a new access": "Ajouter un nouveau rôle",
|
||||||
"Add a team": "Ajouter un groupe",
|
"Add a team": "Ajouter un groupe",
|
||||||
"Add members to the team": "Ajouter des membres à l'équipe",
|
"Add members to the team": "Ajouter des membres à l'équipe",
|
||||||
"Add the domain": "Ajouter le domaine",
|
"Add the domain": "Ajouter le domaine",
|
||||||
"Add the following DNS values:": "Ajouter les valeurs DNS suivantes :",
|
"Add the following DNS values:": "Ajouter les valeurs DNS suivantes :",
|
||||||
"Add to group": "Ajouter au groupe",
|
"Add to group": "Ajouter au groupe",
|
||||||
|
"Add to domain": "Ajouter au domaine",
|
||||||
"Address: National Agency for Territorial Cohesion - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07 Paris": "Adresse : Agence Nationale de la Cohésion des Territoires - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07",
|
"Address: National Agency for Territorial Cohesion - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07 Paris": "Adresse : Agence Nationale de la Cohésion des Territoires - 20, avenue de Ségur TSA 10717 75 334 Paris Cedex 07",
|
||||||
"Administration": "Administration",
|
"Administration": "Administration",
|
||||||
"Administrator": "Administrateur",
|
"Administrator": "Administrateur",
|
||||||
@@ -85,6 +88,7 @@
|
|||||||
"Enable mailbox": "Activer la boîte mail",
|
"Enable mailbox": "Activer la boîte mail",
|
||||||
"Enter the new name of the selected team": "Entrez le nouveau nom du groupe sélectionné",
|
"Enter the new name of the selected team": "Entrez le nouveau nom du groupe sélectionné",
|
||||||
"Example: saint-laurent.fr": "Exemple : saint-laurent.fr",
|
"Example: saint-laurent.fr": "Exemple : saint-laurent.fr",
|
||||||
|
"Failed to add access": "Impossible d'ajouter les accès",
|
||||||
"Failed to add {{name}} in the team": "Impossible d'ajouter {{name}} au groupe",
|
"Failed to add {{name}} in the team": "Impossible d'ajouter {{name}} au groupe",
|
||||||
"Failed to create the invitation for {{email}}": "Impossible de créer l'invitation pour {{email}}",
|
"Failed to create the invitation for {{email}}": "Impossible de créer l'invitation pour {{email}}",
|
||||||
"Failed to fetch domain data": "Impossible de récupérer les données du domaine",
|
"Failed to fetch domain data": "Impossible de récupérer les données du domaine",
|
||||||
@@ -104,7 +108,8 @@
|
|||||||
"Image 404 page not found": "Image 404 page introuvable",
|
"Image 404 page not found": "Image 404 page introuvable",
|
||||||
"Improvement and contact": "Amélioration et contact",
|
"Improvement and contact": "Amélioration et contact",
|
||||||
"Invitation sent to {{email}}": "Invitation envoyée à {{email}}",
|
"Invitation sent to {{email}}": "Invitation envoyée à {{email}}",
|
||||||
"Invite new members to {{teamName}}": "Invitez de nouveaux membres à rejoindre {{teamName}}",
|
"Invite new members to {{teamName}}": "Inviter de nouveaux membres à rejoindre {{teamName}}",
|
||||||
|
"Invite new members with roles": "Inviter de nouveaux membres avec un rôle",
|
||||||
"It must not contain spaces, accents or special characters (except \".\" or \"-\"). E.g.: jean.dupont": "Il ne doit pas contenir d'espaces, d'accents ou de caractères spéciaux (excepté \".\" ou \"-\"). Ex. : jean.dupont",
|
"It must not contain spaces, accents or special characters (except \".\" or \"-\"). E.g.: jean.dupont": "Il ne doit pas contenir d'espaces, d'accents ou de caractères spéciaux (excepté \".\" ou \"-\"). Ex. : jean.dupont",
|
||||||
"It seems that the page you are looking for does not exist or cannot be displayed correctly.": "Il semble que la page que vous cherchez n'existe pas ou ne puisse pas être affichée correctement.",
|
"It seems that the page you are looking for does not exist or cannot be displayed correctly.": "Il semble que la page que vous cherchez n'existe pas ou ne puisse pas être affichée correctement.",
|
||||||
"It's true, you didn't have to click on a block that covers half the page to say you agree to the placement of cookies — even if you don't know what it means!": "C'est vrai, vous n'avez pas à cliquer sur un bloc qui couvre la moitié de la page pour dire que vous acceptez le placement de cookies — même si vous ne savez pas ce que cela signifie !",
|
"It's true, you didn't have to click on a block that covers half the page to say you agree to the placement of cookies — even if you don't know what it means!": "C'est vrai, vous n'avez pas à cliquer sur un bloc qui couvre la moitié de la page pour dire que vous acceptez le placement de cookies — même si vous ne savez pas ce que cela signifie !",
|
||||||
@@ -168,6 +173,7 @@
|
|||||||
"Roles": "Rôles",
|
"Roles": "Rôles",
|
||||||
"Régie": "Régie",
|
"Régie": "Régie",
|
||||||
"Search new members (name or email)": "Rechercher de nouveaux membres (nom ou email)",
|
"Search new members (name or email)": "Rechercher de nouveaux membres (nom ou email)",
|
||||||
|
"Search for members to assign them a role (name or email)": "Rechercher des membres afin de leur attribuer un rôle (nom ou email)",
|
||||||
"Secondary email address": "Adresse e-mail secondaire",
|
"Secondary email address": "Adresse e-mail secondaire",
|
||||||
"Send a letter by post (free of charge, no stamp needed):": "Envoyer un courrier par la poste (gratuit, ne pas mettre de timbre) :",
|
"Send a letter by post (free of charge, no stamp needed):": "Envoyer un courrier par la poste (gratuit, ne pas mettre de timbre) :",
|
||||||
"Something bad happens, please refresh the page.": "Une erreur inattendue s'est produite, rechargez la page.",
|
"Something bad happens, please refresh the page.": "Une erreur inattendue s'est produite, rechargez la page.",
|
||||||
|
|||||||
Reference in New Issue
Block a user