✨(frontend) add modal to update role of a member
Add modal to update role of a member.
This commit is contained in:
@@ -15,7 +15,6 @@ function getCSRFToken() {
|
|||||||
|
|
||||||
export const fetchAPI = async (input: string, init?: RequestInit) => {
|
export const fetchAPI = async (input: string, init?: RequestInit) => {
|
||||||
const apiUrl = `${baseApiUrl()}${input}`;
|
const apiUrl = `${baseApiUrl()}${input}`;
|
||||||
const { logout } = useAuthStore.getState();
|
|
||||||
|
|
||||||
const csrfToken = getCSRFToken();
|
const csrfToken = getCSRFToken();
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@ export const fetchAPI = async (input: string, init?: RequestInit) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
|
const { logout } = useAuthStore.getState();
|
||||||
logout();
|
logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
|
import { Access, Pad, Role } from '@/features/pads/pad-management';
|
||||||
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
|
import { MemberAction } from '../components/MemberAction';
|
||||||
|
|
||||||
|
const access: Access = {
|
||||||
|
id: '789',
|
||||||
|
role: Role.ADMIN,
|
||||||
|
user: {
|
||||||
|
id: '11',
|
||||||
|
email: 'user1@test.com',
|
||||||
|
},
|
||||||
|
team: '',
|
||||||
|
abilities: {
|
||||||
|
set_role_to: [Role.READER, Role.ADMIN],
|
||||||
|
} as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const doc = {
|
||||||
|
id: '123456',
|
||||||
|
title: 'teamName',
|
||||||
|
} as Pad;
|
||||||
|
|
||||||
|
describe('MemberAction', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when owner', async () => {
|
||||||
|
render(
|
||||||
|
<MemberAction access={access} currentRole={Role.OWNER} doc={doc} />,
|
||||||
|
{
|
||||||
|
wrapper: AppWrapper,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByLabelText('Open the member options modal'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when reader', () => {
|
||||||
|
render(
|
||||||
|
<MemberAction access={access} currentRole={Role.READER} doc={doc} />,
|
||||||
|
{
|
||||||
|
wrapper: AppWrapper,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByLabelText('Open the member options modal'),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when editor', () => {
|
||||||
|
render(
|
||||||
|
<MemberAction access={access} currentRole={Role.EDITOR} doc={doc} />,
|
||||||
|
{
|
||||||
|
wrapper: AppWrapper,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByLabelText('Open the member options modal'),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when admin', async () => {
|
||||||
|
render(
|
||||||
|
<MemberAction access={access} currentRole={Role.ADMIN} doc={doc} />,
|
||||||
|
{
|
||||||
|
wrapper: AppWrapper,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByLabelText('Open the member options modal'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when admin to owner', () => {
|
||||||
|
render(
|
||||||
|
<MemberAction
|
||||||
|
access={{ ...access, role: Role.OWNER }}
|
||||||
|
currentRole={Role.ADMIN}
|
||||||
|
doc={doc}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
wrapper: AppWrapper,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByLabelText('Open the member options modal'),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
|
import { useAuthStore } from '@/core/auth';
|
||||||
|
import { Access, Role } from '@/features/pads/pad-management';
|
||||||
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
|
import { ModalRole } from '../components/ModalRole';
|
||||||
|
|
||||||
|
const toast = jest.fn();
|
||||||
|
jest.mock('@openfun/cunningham-react', () => ({
|
||||||
|
...jest.requireActual('@openfun/cunningham-react'),
|
||||||
|
useToastProvider: () => ({
|
||||||
|
toast,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
HTMLDialogElement.prototype.showModal = jest.fn(function mock(
|
||||||
|
this: HTMLDialogElement,
|
||||||
|
) {
|
||||||
|
this.open = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const access: Access = {
|
||||||
|
id: '789',
|
||||||
|
role: Role.ADMIN,
|
||||||
|
team: '123',
|
||||||
|
user: {
|
||||||
|
id: '11',
|
||||||
|
email: 'user1@test.com',
|
||||||
|
},
|
||||||
|
abilities: {
|
||||||
|
set_role_to: [Role.EDITOR, Role.ADMIN],
|
||||||
|
} as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ModalRole', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the cancel button', async () => {
|
||||||
|
const onClose = jest.fn();
|
||||||
|
render(
|
||||||
|
<ModalRole
|
||||||
|
access={access}
|
||||||
|
currentRole={Role.ADMIN}
|
||||||
|
onClose={onClose}
|
||||||
|
docId="123"
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
wrapper: AppWrapper,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await userEvent.click(
|
||||||
|
screen.getByRole('button', {
|
||||||
|
name: 'Cancel',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the role successfully', async () => {
|
||||||
|
fetchMock.mock(`end:/documents/123/accesses/789/`, {
|
||||||
|
status: 200,
|
||||||
|
ok: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClose = jest.fn();
|
||||||
|
render(
|
||||||
|
<ModalRole
|
||||||
|
access={access}
|
||||||
|
currentRole={Role.OWNER}
|
||||||
|
onClose={onClose}
|
||||||
|
docId="123"
|
||||||
|
/>,
|
||||||
|
{ wrapper: AppWrapper },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Administrator',
|
||||||
|
}),
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
await userEvent.click(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Reader',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await userEvent.click(
|
||||||
|
screen.getByRole('button', {
|
||||||
|
name: 'Validate',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(toast).toHaveBeenCalledWith(
|
||||||
|
'The role has been updated',
|
||||||
|
'success',
|
||||||
|
{
|
||||||
|
duration: 4000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fetchMock.lastUrl()).toContain(`/documents/123/accesses/789/`);
|
||||||
|
|
||||||
|
expect(onClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails to update the role', async () => {
|
||||||
|
fetchMock.patchOnce(`end:/documents/123/accesses/789/`, {
|
||||||
|
status: 500,
|
||||||
|
body: {
|
||||||
|
detail: 'The server is totally broken',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ModalRole
|
||||||
|
access={access}
|
||||||
|
currentRole={Role.OWNER}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
docId="123"
|
||||||
|
/>,
|
||||||
|
{ wrapper: AppWrapper },
|
||||||
|
);
|
||||||
|
|
||||||
|
await userEvent.click(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Reader',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await userEvent.click(
|
||||||
|
screen.getByRole('button', {
|
||||||
|
name: 'Validate',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByText('The server is totally broken'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when last owner', () => {
|
||||||
|
useAuthStore.setState({
|
||||||
|
userData: access.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
const access2: Access = {
|
||||||
|
...access,
|
||||||
|
role: Role.OWNER,
|
||||||
|
abilities: {
|
||||||
|
set_role_to: [],
|
||||||
|
} as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ModalRole
|
||||||
|
access={access2}
|
||||||
|
currentRole={Role.OWNER}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
docId="123"
|
||||||
|
/>,
|
||||||
|
{ wrapper: AppWrapper },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText('You are the sole owner of this group.'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
'Make another member the group owner, before you can change your own role.',
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Administrator',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Owner',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Reader',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Editor',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', {
|
||||||
|
name: 'Validate',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when it is another owner', () => {
|
||||||
|
useAuthStore.setState({
|
||||||
|
userData: {
|
||||||
|
id: '12',
|
||||||
|
email: 'username2@test.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const access2: Access = {
|
||||||
|
...access,
|
||||||
|
role: Role.OWNER,
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ModalRole
|
||||||
|
access={access2}
|
||||||
|
currentRole={Role.OWNER}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
docId="123"
|
||||||
|
/>,
|
||||||
|
{ wrapper: AppWrapper },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText('You cannot update the role of other owner.'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Administrator',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Owner',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Reader',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Editor',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', {
|
||||||
|
name: 'Validate',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks the render when current user is admin', () => {
|
||||||
|
render(
|
||||||
|
<ModalRole
|
||||||
|
access={access}
|
||||||
|
currentRole={Role.ADMIN}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
docId="123"
|
||||||
|
/>,
|
||||||
|
{ wrapper: AppWrapper },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Editor',
|
||||||
|
}),
|
||||||
|
).toBeEnabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Reader',
|
||||||
|
}),
|
||||||
|
).toBeEnabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Administrator',
|
||||||
|
}),
|
||||||
|
).toBeEnabled();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('radio', {
|
||||||
|
name: 'Owner',
|
||||||
|
}),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
UseMutationOptions,
|
||||||
|
useMutation,
|
||||||
|
useQueryClient,
|
||||||
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
|
import { Access, KEY_PAD, Role } from '@/features/pads/pad-management';
|
||||||
|
|
||||||
|
import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses';
|
||||||
|
|
||||||
|
interface UpdateDocAccessProps {
|
||||||
|
docId: string;
|
||||||
|
accessId: string;
|
||||||
|
role: Role;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateDocAccess = async ({
|
||||||
|
docId,
|
||||||
|
accessId,
|
||||||
|
role,
|
||||||
|
}: UpdateDocAccessProps): Promise<Access> => {
|
||||||
|
const response = await fetchAPI(`documents/${docId}/accesses/${accessId}/`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify({
|
||||||
|
role,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new APIError('Failed to update role', await errorCauses(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<Access>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseUpdateDocAccess = Partial<Access>;
|
||||||
|
|
||||||
|
type UseUpdateDocAccessOptions = UseMutationOptions<
|
||||||
|
Access,
|
||||||
|
APIError,
|
||||||
|
UseUpdateDocAccess
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useUpdateDocAccess = (options?: UseUpdateDocAccessOptions) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<Access, APIError, UpdateDocAccessProps>({
|
||||||
|
mutationFn: updateDocAccess,
|
||||||
|
...options,
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [KEY_LIST_DOC_ACCESSES],
|
||||||
|
});
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [KEY_PAD],
|
||||||
|
});
|
||||||
|
if (options?.onSuccess) {
|
||||||
|
options.onSuccess(data, variables, context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error, variables, context) => {
|
||||||
|
if (options?.onError) {
|
||||||
|
options.onError(error, variables, context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { Button } from '@openfun/cunningham-react';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { Box, DropButton, IconOptions, Text } from '@/components';
|
||||||
|
import { Access, Pad, Role } from '@/features/pads/pad-management';
|
||||||
|
|
||||||
|
import { ModalDelete } from './ModalDelete';
|
||||||
|
import { ModalRole } from './ModalRole';
|
||||||
|
|
||||||
|
interface MemberActionProps {
|
||||||
|
access: Access;
|
||||||
|
currentRole: Role;
|
||||||
|
doc: Pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MemberAction = ({
|
||||||
|
access,
|
||||||
|
currentRole,
|
||||||
|
doc,
|
||||||
|
}: MemberActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isModalRoleOpen, setIsModalRoleOpen] = useState(false);
|
||||||
|
const [isModalDeleteOpen, setIsModalDeleteOpen] = useState(false);
|
||||||
|
const [isDropOpen, setIsDropOpen] = useState(false);
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentRole === Role.EDITOR ||
|
||||||
|
currentRole === Role.READER ||
|
||||||
|
(access.role === Role.OWNER && currentRole === Role.ADMIN)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropButton
|
||||||
|
button={
|
||||||
|
<IconOptions
|
||||||
|
isOpen={isDropOpen}
|
||||||
|
aria-label={t('Open the member options modal')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
|
||||||
|
isOpen={isDropOpen}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
aria-label={t('Open the modal to update the role of this member')}
|
||||||
|
onClick={() => {
|
||||||
|
setIsModalRoleOpen(true);
|
||||||
|
setIsDropOpen(false);
|
||||||
|
}}
|
||||||
|
color="primary-text"
|
||||||
|
icon={<span className="material-icons">edit</span>}
|
||||||
|
>
|
||||||
|
<Text $theme="primary">{t('Update role')}</Text>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-label={t('Open the modal to delete this member')}
|
||||||
|
onClick={() => {
|
||||||
|
setIsModalDeleteOpen(true);
|
||||||
|
setIsDropOpen(false);
|
||||||
|
}}
|
||||||
|
color="primary-text"
|
||||||
|
icon={<span className="material-icons">delete</span>}
|
||||||
|
>
|
||||||
|
<Text $theme="primary">{t('Remove from group')}</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</DropButton>
|
||||||
|
{isModalRoleOpen && (
|
||||||
|
<ModalRole
|
||||||
|
access={access}
|
||||||
|
currentRole={currentRole}
|
||||||
|
onClose={() => setIsModalRoleOpen(false)}
|
||||||
|
docId={doc.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isModalDeleteOpen && (
|
||||||
|
<ModalDelete
|
||||||
|
access={access}
|
||||||
|
currentRole={currentRole}
|
||||||
|
onClose={() => setIsModalDeleteOpen(false)}
|
||||||
|
doc={doc}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalSize,
|
||||||
|
VariantType,
|
||||||
|
useToastProvider,
|
||||||
|
} from '@openfun/cunningham-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
|
import { Access, Role } from '@/features/pads/pad-management';
|
||||||
|
|
||||||
|
import { ChooseRole } from '../../members-add/components/ChooseRole';
|
||||||
|
import { useUpdateDocAccess } from '../api';
|
||||||
|
import { useWhoAmI } from '../hooks/useWhoAmI';
|
||||||
|
|
||||||
|
interface ModalRoleProps {
|
||||||
|
access: Access;
|
||||||
|
currentRole: Role;
|
||||||
|
onClose: () => void;
|
||||||
|
docId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModalRole = ({
|
||||||
|
access,
|
||||||
|
currentRole,
|
||||||
|
onClose,
|
||||||
|
docId,
|
||||||
|
}: ModalRoleProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [localRole, setLocalRole] = useState(access.role);
|
||||||
|
const { toast } = useToastProvider();
|
||||||
|
const {
|
||||||
|
mutate: updateDocAccess,
|
||||||
|
error: errorUpdate,
|
||||||
|
isError: isErrorUpdate,
|
||||||
|
isPending,
|
||||||
|
} = useUpdateDocAccess({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast(t('The role has been updated'), VariantType.SUCCESS, {
|
||||||
|
duration: 4000,
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { isLastOwner, isOtherOwner } = useWhoAmI(access);
|
||||||
|
|
||||||
|
const isNotAllowed = isOtherOwner || isLastOwner;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen
|
||||||
|
leftActions={
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => onClose()}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{t('Cancel')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
onClose={() => onClose()}
|
||||||
|
closeOnClickOutside
|
||||||
|
hideCloseButton
|
||||||
|
rightActions={
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => {
|
||||||
|
updateDocAccess({
|
||||||
|
role: localRole,
|
||||||
|
docId,
|
||||||
|
accessId: access.id,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={isNotAllowed || isPending}
|
||||||
|
>
|
||||||
|
{t('Validate')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size={ModalSize.MEDIUM}
|
||||||
|
title={t('Update the role')}
|
||||||
|
>
|
||||||
|
<Box aria-label={t('Radio buttons to update the roles')}>
|
||||||
|
{isErrorUpdate && (
|
||||||
|
<TextErrors
|
||||||
|
$margin={{ bottom: 'small' }}
|
||||||
|
causes={errorUpdate.cause}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(isLastOwner || isOtherOwner) && (
|
||||||
|
<Text
|
||||||
|
$theme="warning"
|
||||||
|
$direction="row"
|
||||||
|
$align="center"
|
||||||
|
$gap="0.5rem"
|
||||||
|
$margin={{ bottom: 'tiny', top: 'none' }}
|
||||||
|
as="div"
|
||||||
|
>
|
||||||
|
<span className="material-icons">warning</span>
|
||||||
|
{isLastOwner && (
|
||||||
|
<Box $align="flex-start">
|
||||||
|
<Text $theme="warning">
|
||||||
|
{t('You are the sole owner of this group.')}
|
||||||
|
</Text>
|
||||||
|
<Text $theme="warning">
|
||||||
|
{t(
|
||||||
|
'Make another member the group owner, before you can change your own role.',
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isOtherOwner && t('You cannot update the role of other owner.')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ChooseRole
|
||||||
|
defaultRole={access.role}
|
||||||
|
currentRole={currentRole}
|
||||||
|
disabled={isNotAllowed}
|
||||||
|
setRole={setLocalRole}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { useAuthStore } from '@/core/auth';
|
||||||
|
import { Access, Role } from '@/features/pads/pad-management';
|
||||||
|
|
||||||
|
export const useWhoAmI = (access: Access) => {
|
||||||
|
const { userData } = useAuthStore();
|
||||||
|
|
||||||
|
const isMyself = userData?.id === access.user.id;
|
||||||
|
const rolesAllowed = access.abilities.set_role_to;
|
||||||
|
|
||||||
|
const isLastOwner =
|
||||||
|
!rolesAllowed.length && access.role === Role.OWNER && isMyself;
|
||||||
|
|
||||||
|
const isOtherOwner = access.role === Role.OWNER && userData?.id && !isMyself;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLastOwner,
|
||||||
|
isOtherOwner,
|
||||||
|
isMyself,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user