diff --git a/src/frontend/apps/impress/src/features/members/__tests__/MemberAction.test.tsx b/src/frontend/apps/impress/src/features/members/__tests__/MemberAction.test.tsx
deleted file mode 100644
index 780ce6eb..00000000
--- a/src/frontend/apps/impress/src/features/members/__tests__/MemberAction.test.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import '@testing-library/jest-dom';
-import { render, screen } from '@testing-library/react';
-import fetchMock from 'fetch-mock';
-
-import { Role } from '@/features/teams';
-import { AppWrapper } from '@/tests/utils';
-
-import { MemberAction } from '../components/MemberAction';
-import { Access } from '../types';
-
-const access: Access = {
- id: '789',
- role: Role.ADMIN,
- user: {
- id: '11',
- name: 'username1',
- email: 'user1@test.com',
- },
- abilities: {
- set_role_to: [Role.MEMBER, Role.ADMIN],
- } as any,
-};
-
-describe('MemberAction', () => {
- afterEach(() => {
- fetchMock.restore();
- });
-
- it('checks the render when owner', async () => {
- render(
- ,
- {
- wrapper: AppWrapper,
- },
- );
-
- expect(
- await screen.findByLabelText('Open the member options modal'),
- ).toBeInTheDocument();
- });
-
- it('checks the render when member', () => {
- render(
- ,
- {
- wrapper: AppWrapper,
- },
- );
-
- expect(
- screen.queryByLabelText('Open the member options modal'),
- ).not.toBeInTheDocument();
- });
-
- it('checks the render when admin', async () => {
- render(
- ,
- {
- wrapper: AppWrapper,
- },
- );
-
- expect(
- await screen.findByLabelText('Open the member options modal'),
- ).toBeInTheDocument();
- });
-
- it('checks the render when admin to owner', () => {
- render(
- ,
- {
- wrapper: AppWrapper,
- },
- );
-
- expect(
- screen.queryByLabelText('Open the member options modal'),
- ).not.toBeInTheDocument();
- });
-});
diff --git a/src/frontend/apps/impress/src/features/members/__tests__/MemberGrid.test.tsx b/src/frontend/apps/impress/src/features/members/__tests__/MemberGrid.test.tsx
deleted file mode 100644
index 9c8b1745..00000000
--- a/src/frontend/apps/impress/src/features/members/__tests__/MemberGrid.test.tsx
+++ /dev/null
@@ -1,348 +0,0 @@
-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 { Role, Team } from '@/features/teams';
-import { AppWrapper } from '@/tests/utils';
-
-import { MemberGrid } from '../components/MemberGrid';
-import { Access } from '../types';
-
-const team = {
- id: '123456',
- name: 'teamName',
-} as Team;
-
-describe('MemberGrid', () => {
- afterEach(() => {
- fetchMock.restore();
- });
-
- it('renders with no member to display', async () => {
- fetchMock.mock(`/api/teams/123456/accesses/?page=1`, {
- count: 0,
- results: [],
- });
-
- render(, {
- wrapper: AppWrapper,
- });
-
- expect(screen.getByRole('status')).toBeInTheDocument();
-
- expect(await screen.findByRole('img')).toHaveAttribute(
- 'alt',
- 'Illustration of an empty table',
- );
-
- expect(screen.getByText('This table is empty')).toBeInTheDocument();
- expect(
- screen.getByLabelText('Add members to the team'),
- ).toBeInTheDocument();
- });
-
- it('checks the render with members', async () => {
- const accesses: Access[] = [
- {
- id: '1',
- role: Role.OWNER,
- user: {
- id: '11',
- name: 'username1',
- email: 'user1@test.com',
- },
- abilities: {} as any,
- },
- {
- id: '2',
- role: Role.MEMBER,
- user: {
- id: '22',
- name: 'username2',
- email: 'user2@test.com',
- },
- abilities: {} as any,
- },
- {
- id: '32',
- role: Role.ADMIN,
- user: {
- id: '33',
- name: 'username3',
- email: 'user3@test.com',
- },
- abilities: {} as any,
- },
- ];
-
- fetchMock.mock(`/api/teams/123456/accesses/?page=1`, {
- count: 3,
- results: accesses,
- });
-
- render(, {
- wrapper: AppWrapper,
- });
-
- expect(screen.getByRole('status')).toBeInTheDocument();
-
- expect(await screen.findByText('username1')).toBeInTheDocument();
- expect(screen.getByText('username2')).toBeInTheDocument();
- expect(screen.getByText('username3')).toBeInTheDocument();
- expect(screen.getByText('user1@test.com')).toBeInTheDocument();
- expect(screen.getByText('user2@test.com')).toBeInTheDocument();
- expect(screen.getByText('user3@test.com')).toBeInTheDocument();
- expect(screen.getByText('Owner')).toBeInTheDocument();
- expect(screen.getByText('Admin')).toBeInTheDocument();
- expect(screen.getByText('Member')).toBeInTheDocument();
- });
-
- it('checks the pagination', async () => {
- fetchMock.get(`begin:/api/teams/123456/accesses/?page=`, {
- count: 40,
- results: Array.from({ length: 20 }, (_, i) => ({
- id: i,
- role: Role.OWNER,
- user: {
- id: i,
- name: 'username' + i,
- email: `user${i}@test.com`,
- },
- abilities: {} as any,
- })),
- });
-
- render(, {
- wrapper: AppWrapper,
- });
-
- expect(screen.getByRole('status')).toBeInTheDocument();
-
- expect(fetchMock.lastUrl()).toBe('/api/teams/123456/accesses/?page=1');
-
- expect(
- await screen.findByLabelText('You are currently on page 1'),
- ).toBeInTheDocument();
-
- await userEvent.click(screen.getByLabelText('Go to page 2'));
-
- expect(
- await screen.findByLabelText('You are currently on page 2'),
- ).toBeInTheDocument();
-
- expect(fetchMock.lastUrl()).toBe('/api/teams/123456/accesses/?page=2');
- });
-
- [
- {
- role: Role.OWNER,
- expected: true,
- },
- {
- role: Role.MEMBER,
- expected: false,
- },
- {
- role: Role.ADMIN,
- expected: true,
- },
- ].forEach(({ role, expected }) => {
- it(`checks action button when ${role}`, async () => {
- fetchMock.get(`begin:/api/teams/123456/accesses/?page=`, {
- count: 1,
- results: [
- {
- id: 1,
- role: Role.ADMIN,
- user: {
- id: 1,
- name: 'username1',
- email: `user1@test.com`,
- },
- abilities: {} as any,
- },
- ],
- });
-
- render(, {
- wrapper: AppWrapper,
- });
-
- expect(screen.getByRole('status')).toBeInTheDocument();
-
- /* eslint-disable jest/no-conditional-expect */
- if (expected) {
- expect(
- await screen.findAllByRole('button', {
- name: 'Open the member options modal',
- }),
- ).toBeDefined();
- } else {
- expect(
- screen.queryByRole('button', {
- name: 'Open the member options modal',
- }),
- ).not.toBeInTheDocument();
- }
- /* eslint-enable jest/no-conditional-expect */
- });
- });
-
- it('controls the render when api error', async () => {
- fetchMock.mock(`/api/teams/123456/accesses/?page=1`, {
- status: 500,
- body: {
- cause: 'All broken :(',
- },
- });
-
- render(, {
- wrapper: AppWrapper,
- });
-
- expect(screen.getByRole('status')).toBeInTheDocument();
-
- expect(await screen.findByText('All broken :(')).toBeInTheDocument();
- });
-
- it('cannot add members when current role is member', () => {
- fetchMock.get(`/api/teams/123456/accesses/?page=1`, 200);
-
- render(, {
- wrapper: AppWrapper,
- });
-
- expect(
- screen.queryByLabelText('Add members to the team'),
- ).not.toBeInTheDocument();
- });
-
- it.each([
- ['name', 'Names'],
- ['email', 'Emails'],
- ['role', 'Roles'],
- ])('checks the sorting', async (ordering, header_name) => {
- const mockedData = [
- {
- id: '123',
- role: Role.ADMIN,
- user: {
- id: '123',
- name: 'albert',
- email: 'albert@test.com',
- },
- abilities: {} as any,
- },
- {
- id: '789',
- role: Role.OWNER,
- user: {
- id: '456',
- name: 'philipp',
- email: 'philipp@test.com',
- },
- abilities: {} as any,
- },
- {
- id: '456',
- role: Role.MEMBER,
- user: {
- id: '789',
- name: 'fany',
- email: 'fany@test.com',
- },
- abilities: {} as any,
- },
- ];
-
- const sortedMockedData = [...mockedData].sort((a, b) =>
- a.id > b.id ? 1 : -1,
- );
- const reversedMockedData = [...sortedMockedData].reverse();
-
- fetchMock.get(`/api/teams/123456/accesses/?page=1`, {
- count: 3,
- results: mockedData,
- });
-
- fetchMock.get(`/api/teams/123456/accesses/?page=1&ordering=${ordering}`, {
- count: 3,
- results: sortedMockedData,
- });
-
- fetchMock.get(`/api/teams/123456/accesses/?page=1&ordering=-${ordering}`, {
- count: 3,
- results: reversedMockedData,
- });
-
- render(, {
- wrapper: AppWrapper,
- });
-
- expect(screen.getByRole('status')).toBeInTheDocument();
-
- expect(fetchMock.lastUrl()).toBe(`/api/teams/123456/accesses/?page=1`);
-
- await waitFor(() => {
- expect(screen.queryByRole('status')).not.toBeInTheDocument();
- });
-
- let rows = screen.getAllByRole('row');
- expect(rows[1]).toHaveTextContent('albert');
- expect(rows[2]).toHaveTextContent('philipp');
- expect(rows[3]).toHaveTextContent('fany');
-
- expect(screen.queryByLabelText('arrow_drop_down')).not.toBeInTheDocument();
- expect(screen.queryByLabelText('arrow_drop_up')).not.toBeInTheDocument();
-
- await userEvent.click(screen.getByText(header_name));
-
- expect(fetchMock.lastUrl()).toBe(
- `/api/teams/123456/accesses/?page=1&ordering=${ordering}`,
- );
-
- await waitFor(() => {
- expect(screen.queryByRole('status')).not.toBeInTheDocument();
- });
-
- rows = screen.getAllByRole('row');
- expect(rows[1]).toHaveTextContent('albert');
- expect(rows[2]).toHaveTextContent('fany');
- expect(rows[3]).toHaveTextContent('philipp');
-
- expect(await screen.findByText('arrow_drop_up')).toBeInTheDocument();
-
- await userEvent.click(screen.getByText(header_name));
-
- expect(fetchMock.lastUrl()).toBe(
- `/api/teams/123456/accesses/?page=1&ordering=-${ordering}`,
- );
- await waitFor(() => {
- expect(screen.queryByRole('status')).not.toBeInTheDocument();
- });
-
- rows = screen.getAllByRole('row');
- expect(rows[1]).toHaveTextContent('philipp');
- expect(rows[2]).toHaveTextContent('fany');
- expect(rows[3]).toHaveTextContent('albert');
-
- expect(await screen.findByText('arrow_drop_down')).toBeInTheDocument();
-
- await userEvent.click(screen.getByText(header_name));
-
- expect(fetchMock.lastUrl()).toBe('/api/teams/123456/accesses/?page=1');
-
- await waitFor(() => {
- expect(screen.queryByRole('status')).not.toBeInTheDocument();
- });
-
- rows = screen.getAllByRole('row');
- expect(rows[1]).toHaveTextContent('albert');
- expect(rows[2]).toHaveTextContent('philipp');
- expect(rows[3]).toHaveTextContent('fany');
-
- expect(screen.queryByLabelText('arrow_drop_down')).not.toBeInTheDocument();
- expect(screen.queryByLabelText('arrow_drop_up')).not.toBeInTheDocument();
- });
-});
diff --git a/src/frontend/apps/impress/src/features/members/__tests__/ModalRole.test.tsx b/src/frontend/apps/impress/src/features/members/__tests__/ModalRole.test.tsx
deleted file mode 100644
index 0716b663..00000000
--- a/src/frontend/apps/impress/src/features/members/__tests__/ModalRole.test.tsx
+++ /dev/null
@@ -1,287 +0,0 @@
-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 { Role } from '@/features/teams';
-import { AppWrapper } from '@/tests/utils';
-
-import { ModalRole } from '../components/ModalRole';
-import { Access } from '../types';
-
-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,
- user: {
- id: '11',
- name: 'username1',
- email: 'user1@test.com',
- },
- abilities: {
- set_role_to: [Role.MEMBER, Role.ADMIN],
- } as any,
-};
-
-describe('ModalRole', () => {
- afterEach(() => {
- fetchMock.restore();
- });
-
- it('checks the cancel button', async () => {
- const onClose = jest.fn();
- render(
- ,
- {
- wrapper: AppWrapper,
- },
- );
-
- await userEvent.click(
- screen.getByRole('button', {
- name: 'Cancel',
- }),
- );
-
- expect(onClose).toHaveBeenCalled();
- });
-
- it('updates the role successfully', async () => {
- fetchMock.patchOnce(`/api/teams/123/accesses/789/`, {
- status: 200,
- ok: true,
- });
-
- const onClose = jest.fn();
- render(
- ,
- { wrapper: AppWrapper },
- );
-
- expect(
- screen.getByRole('radio', {
- name: 'Admin',
- }),
- ).toBeChecked();
-
- await userEvent.click(
- screen.getByRole('radio', {
- name: 'Member',
- }),
- );
-
- await userEvent.click(
- screen.getByRole('button', {
- name: 'Validate',
- }),
- );
-
- await waitFor(() => {
- expect(toast).toHaveBeenCalledWith(
- 'The role has been updated',
- 'success',
- {
- duration: 4000,
- },
- );
- });
-
- expect(fetchMock.lastUrl()).toBe(`/api/teams/123/accesses/789/`);
-
- expect(onClose).toHaveBeenCalled();
- });
-
- it('fails to update the role', async () => {
- fetchMock.patchOnce(`/api/teams/123/accesses/789/`, {
- status: 500,
- body: {
- detail: 'The server is totally broken',
- },
- });
-
- render(
- ,
- { wrapper: AppWrapper },
- );
-
- await userEvent.click(
- screen.getByRole('radio', {
- name: 'Member',
- }),
- );
-
- 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(
- ,
- { wrapper: AppWrapper },
- );
-
- expect(
- screen.getByText('You are the last owner, you cannot change your role.'),
- ).toBeInTheDocument();
-
- expect(
- screen.getByRole('radio', {
- name: 'Admin',
- }),
- ).toBeDisabled();
-
- expect(
- screen.getByRole('radio', {
- name: 'Owner',
- }),
- ).toBeDisabled();
-
- expect(
- screen.getByRole('radio', {
- name: 'Member',
- }),
- ).toBeDisabled();
-
- expect(
- screen.getByRole('button', {
- name: 'Validate',
- }),
- ).toBeDisabled();
- });
-
- it('checks the render when it is another owner', () => {
- useAuthStore.setState({
- userData: {
- id: '12',
- name: 'username2',
- email: 'username2@test.com',
- },
- });
-
- const access2: Access = {
- ...access,
- role: Role.OWNER,
- };
-
- render(
- ,
- { wrapper: AppWrapper },
- );
-
- expect(
- screen.getByText('You cannot update the role of other owner.'),
- ).toBeInTheDocument();
-
- expect(
- screen.getByRole('radio', {
- name: 'Admin',
- }),
- ).toBeDisabled();
-
- expect(
- screen.getByRole('radio', {
- name: 'Owner',
- }),
- ).toBeDisabled();
-
- expect(
- screen.getByRole('radio', {
- name: 'Member',
- }),
- ).toBeDisabled();
-
- expect(
- screen.getByRole('button', {
- name: 'Validate',
- }),
- ).toBeDisabled();
- });
-
- it('checks the render when current user is admin', () => {
- render(
- ,
- { wrapper: AppWrapper },
- );
-
- expect(
- screen.getByRole('radio', {
- name: 'Member',
- }),
- ).toBeEnabled();
-
- expect(
- screen.getByRole('radio', {
- name: 'Admin',
- }),
- ).toBeEnabled();
-
- expect(
- screen.getByRole('radio', {
- name: 'Owner',
- }),
- ).toBeDisabled();
- });
-});
diff --git a/src/frontend/apps/impress/src/features/members/api/index.ts b/src/frontend/apps/impress/src/features/members/api/index.ts
deleted file mode 100644
index 1a8460f8..00000000
--- a/src/frontend/apps/impress/src/features/members/api/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './useDeleteTeamAccess';
-export * from './useTeamsAccesses';
-export * from './useUpdateTeamAccess';
diff --git a/src/frontend/apps/impress/src/features/members/api/useDeleteTeamAccess.ts b/src/frontend/apps/impress/src/features/members/api/useDeleteTeamAccess.ts
deleted file mode 100644
index 0a81cfbe..00000000
--- a/src/frontend/apps/impress/src/features/members/api/useDeleteTeamAccess.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
- UseMutationOptions,
- useMutation,
- useQueryClient,
-} from '@tanstack/react-query';
-
-import { APIError, errorCauses, fetchAPI } from '@/api';
-import { KEY_LIST_TEAM, KEY_TEAM } from '@/features/teams/';
-
-import { KEY_LIST_TEAM_ACCESSES } from './useTeamsAccesses';
-
-interface DeleteTeamAccessProps {
- teamId: string;
- accessId: string;
-}
-
-export const deleteTeamAccess = async ({
- teamId,
- accessId,
-}: DeleteTeamAccessProps): Promise => {
- const response = await fetchAPI(`teams/${teamId}/accesses/${accessId}/`, {
- method: 'DELETE',
- });
-
- if (!response.ok) {
- throw new APIError(
- 'Failed to delete the member',
- await errorCauses(response),
- );
- }
-};
-
-type UseDeleteTeamAccessOptions = UseMutationOptions<
- void,
- APIError,
- DeleteTeamAccessProps
->;
-
-export const useDeleteTeamAccess = (options?: UseDeleteTeamAccessOptions) => {
- const queryClient = useQueryClient();
- return useMutation({
- mutationFn: deleteTeamAccess,
- ...options,
- onSuccess: (data, variables, context) => {
- void queryClient.invalidateQueries({
- queryKey: [KEY_LIST_TEAM_ACCESSES],
- });
- void queryClient.invalidateQueries({
- queryKey: [KEY_TEAM],
- });
- void queryClient.invalidateQueries({
- queryKey: [KEY_LIST_TEAM],
- });
- if (options?.onSuccess) {
- options.onSuccess(data, variables, context);
- }
- },
- onError: (error, variables, context) => {
- if (options?.onError) {
- options.onError(error, variables, context);
- }
- },
- });
-};
diff --git a/src/frontend/apps/impress/src/features/members/api/useTeamsAccesses.tsx b/src/frontend/apps/impress/src/features/members/api/useTeamsAccesses.tsx
deleted file mode 100644
index ee4f4ea2..00000000
--- a/src/frontend/apps/impress/src/features/members/api/useTeamsAccesses.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { UseQueryOptions, useQuery } from '@tanstack/react-query';
-
-import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
-
-import { Access } from '../types';
-
-export type TeamAccessesAPIParams = {
- page: number;
- teamId: string;
- ordering?: string;
-};
-
-type AccessesResponse = APIList;
-
-export const getTeamAccesses = async ({
- page,
- teamId,
- ordering,
-}: TeamAccessesAPIParams): Promise => {
- let url = `teams/${teamId}/accesses/?page=${page}`;
-
- if (ordering) {
- url += '&ordering=' + ordering;
- }
-
- const response = await fetchAPI(url);
-
- if (!response.ok) {
- throw new APIError(
- 'Failed to get the team accesses',
- await errorCauses(response),
- );
- }
-
- return response.json() as Promise;
-};
-
-export const KEY_LIST_TEAM_ACCESSES = 'teams-accesses';
-
-export function useTeamAccesses(
- params: TeamAccessesAPIParams,
- queryConfig?: UseQueryOptions,
-) {
- return useQuery({
- queryKey: [KEY_LIST_TEAM_ACCESSES, params],
- queryFn: () => getTeamAccesses(params),
- ...queryConfig,
- });
-}
diff --git a/src/frontend/apps/impress/src/features/members/api/useUpdateTeamAccess.ts b/src/frontend/apps/impress/src/features/members/api/useUpdateTeamAccess.ts
deleted file mode 100644
index 1c501fdb..00000000
--- a/src/frontend/apps/impress/src/features/members/api/useUpdateTeamAccess.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import {
- UseMutationOptions,
- useMutation,
- useQueryClient,
-} from '@tanstack/react-query';
-
-import { APIError, errorCauses, fetchAPI } from '@/api';
-import { KEY_TEAM, Role } from '@/features/teams/';
-
-import { Access } from '../types';
-
-import { KEY_LIST_TEAM_ACCESSES } from './useTeamsAccesses';
-
-interface UpdateTeamAccessProps {
- teamId: string;
- accessId: string;
- role: Role;
-}
-
-export const updateTeamAccess = async ({
- teamId,
- accessId,
- role,
-}: UpdateTeamAccessProps): Promise => {
- const response = await fetchAPI(`teams/${teamId}/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;
-};
-
-type UseUpdateTeamAccess = Partial;
-
-type UseUpdateTeamAccessOptions = UseMutationOptions<
- Access,
- APIError,
- UseUpdateTeamAccess
->;
-
-export const useUpdateTeamAccess = (options?: UseUpdateTeamAccessOptions) => {
- const queryClient = useQueryClient();
- return useMutation({
- mutationFn: updateTeamAccess,
- ...options,
- onSuccess: (data, variables, context) => {
- void queryClient.invalidateQueries({
- queryKey: [KEY_LIST_TEAM_ACCESSES],
- });
- void queryClient.invalidateQueries({
- queryKey: [KEY_TEAM],
- });
- if (options?.onSuccess) {
- options.onSuccess(data, variables, context);
- }
- },
- onError: (error, variables, context) => {
- if (options?.onError) {
- options.onError(error, variables, context);
- }
- },
- });
-};
diff --git a/src/frontend/apps/impress/src/features/members/assets/icon-remove-member.svg b/src/frontend/apps/impress/src/features/members/assets/icon-remove-member.svg
deleted file mode 100644
index 4316c35c..00000000
--- a/src/frontend/apps/impress/src/features/members/assets/icon-remove-member.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/src/frontend/apps/impress/src/features/members/components/ChooseRole.tsx b/src/frontend/apps/impress/src/features/members/components/ChooseRole.tsx
deleted file mode 100644
index fdafeb57..00000000
--- a/src/frontend/apps/impress/src/features/members/components/ChooseRole.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Radio, RadioGroup } from '@openfun/cunningham-react';
-import { useTranslation } from 'react-i18next';
-
-import { Role } from '@/features/teams';
-
-interface ChooseRoleProps {
- currentRole: Role;
- disabled: boolean;
- defaultRole: Role;
- setRole: (role: Role) => void;
-}
-
-export const ChooseRole = ({
- defaultRole,
- disabled,
- currentRole,
- setRole,
-}: ChooseRoleProps) => {
- const { t } = useTranslation();
-
- return (
-
- setRole(evt.target.value as Role)}
- defaultChecked={defaultRole === Role.ADMIN}
- disabled={disabled}
- />
- setRole(evt.target.value as Role)}
- defaultChecked={defaultRole === Role.MEMBER}
- disabled={disabled}
- />
- setRole(evt.target.value as Role)}
- defaultChecked={defaultRole === Role.OWNER}
- disabled={disabled || currentRole !== Role.OWNER}
- />
-
- );
-};
diff --git a/src/frontend/apps/impress/src/features/members/components/MemberAction.tsx b/src/frontend/apps/impress/src/features/members/components/MemberAction.tsx
deleted file mode 100644
index 6a06da5b..00000000
--- a/src/frontend/apps/impress/src/features/members/components/MemberAction.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { Button } from '@openfun/cunningham-react';
-import React, { useState } from 'react';
-import { useTranslation } from 'react-i18next';
-
-import { Box, DropButton, IconOptions, Text } from '@/components';
-import { Role, Team } from '@/features/teams';
-
-import { Access } from '../types';
-
-import { ModalDelete } from './ModalDelete';
-import { ModalRole } from './ModalRole';
-
-interface MemberActionProps {
- access: Access;
- currentRole: Role;
- team: Team;
-}
-
-export const MemberAction = ({
- access,
- currentRole,
- team,
-}: MemberActionProps) => {
- const { t } = useTranslation();
- const [isModalRoleOpen, setIsModalRoleOpen] = useState(false);
- const [isModalDeleteOpen, setIsModalDeleteOpen] = useState(false);
- const [isDropOpen, setIsDropOpen] = useState(false);
-
- if (
- currentRole === Role.MEMBER ||
- (access.role === Role.OWNER && currentRole === Role.ADMIN)
- ) {
- return null;
- }
-
- return (
- <>
-
- }
- onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
- isOpen={isDropOpen}
- >
-
-
-
-
-
- {isModalRoleOpen && (
- setIsModalRoleOpen(false)}
- teamId={team.id}
- />
- )}
- {isModalDeleteOpen && (
- setIsModalDeleteOpen(false)}
- team={team}
- />
- )}
- >
- );
-};
diff --git a/src/frontend/apps/impress/src/features/members/components/MemberGrid.tsx b/src/frontend/apps/impress/src/features/members/components/MemberGrid.tsx
deleted file mode 100644
index 2baea306..00000000
--- a/src/frontend/apps/impress/src/features/members/components/MemberGrid.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-import {
- Button,
- DataGrid,
- SortModel,
- usePagination,
-} from '@openfun/cunningham-react';
-import React, { useEffect, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-
-import IconUser from '@/assets/icons/icon-user.svg';
-import { Box, Card, TextErrors } from '@/components';
-import { useCunninghamTheme } from '@/cunningham';
-import { ModalAddMembers } from '@/features/addMembers';
-import { Role, Team } from '@/features/teams';
-
-import { useTeamAccesses } from '../api/';
-import { PAGE_SIZE } from '../conf';
-
-import { MemberAction } from './MemberAction';
-
-interface MemberGridProps {
- team: Team;
- currentRole: Role;
-}
-
-// FIXME : ask Cunningham to export this type
-type SortModelItem = {
- field: string;
- sort: 'asc' | 'desc' | null;
-};
-
-const defaultOrderingMapping: Record = {
- 'user.name': 'name',
- 'user.email': 'email',
- localizedRole: 'role',
-};
-
-/**
- * Formats the sorting model based on a given mapping.
- * @param {SortModelItem} sortModel The sorting model item containing field and sort direction.
- * @param {Record} mapping The mapping object to map field names.
- * @returns {string} The formatted sorting string.
- */
-function formatSortModel(
- sortModel: SortModelItem,
- mapping = defaultOrderingMapping,
-) {
- const { field, sort } = sortModel;
- const orderingField = mapping[field] || field;
- return sort === 'desc' ? `-${orderingField}` : orderingField;
-}
-
-export const MemberGrid = ({ team, currentRole }: MemberGridProps) => {
- const [isModalMemberOpen, setIsModalMemberOpen] = useState(false);
- const { t } = useTranslation();
- const { colorsTokens } = useCunninghamTheme();
- const pagination = usePagination({
- pageSize: PAGE_SIZE,
- });
- const [sortModel, setSortModel] = useState([]);
- const { page, pageSize, setPagesCount } = pagination;
-
- const ordering = sortModel.length ? formatSortModel(sortModel[0]) : undefined;
-
- const { data, isLoading, error } = useTeamAccesses({
- teamId: team.id,
- page,
- ordering,
- });
-
- const localizedRoles = {
- [Role.ADMIN]: t('Admin'),
- [Role.MEMBER]: t('Member'),
- [Role.OWNER]: t('Owner'),
- };
-
- /*
- * Bug occurs from the Cunningham Datagrid component, when applying sorting
- * on null values. Sanitize empty values to ensure consistent sorting functionality.
- */
- const accesses =
- data?.results?.map((access) => ({
- ...access,
- localizedRole: localizedRoles[access.role],
- user: {
- ...access.user,
- name: access.user.name || '',
- email: access.user.email || '',
- },
- })) || [];
-
- useEffect(() => {
- setPagesCount(data?.count ? Math.ceil(data.count / pageSize) : 0);
- }, [data?.count, pageSize, setPagesCount]);
-
- return (
- <>
- {currentRole !== Role.MEMBER && (
-
-
-
- )}
-
- {error && }
-
-
-
-
- );
- },
- },
- {
- headerName: t('Names'),
- field: 'user.name',
- },
- {
- field: 'user.email',
- headerName: t('Emails'),
- },
- {
- field: 'localizedRole',
- headerName: t('Roles'),
- },
- {
- id: 'column-actions',
- renderCell: ({ row }) => {
- return (
-
- );
- },
- },
- ]}
- rows={accesses}
- isLoading={isLoading}
- pagination={pagination}
- onSortModelChange={setSortModel}
- sortModel={sortModel}
- />
-
- {isModalMemberOpen && (
- setIsModalMemberOpen(false)}
- team={team}
- />
- )}
- >
- );
-};
diff --git a/src/frontend/apps/impress/src/features/members/components/ModalDelete.tsx b/src/frontend/apps/impress/src/features/members/components/ModalDelete.tsx
deleted file mode 100644
index bd8954a4..00000000
--- a/src/frontend/apps/impress/src/features/members/components/ModalDelete.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import {
- Button,
- Modal,
- ModalSize,
- VariantType,
- useToastProvider,
-} from '@openfun/cunningham-react';
-import { t } from 'i18next';
-import { useRouter } from 'next/navigation';
-
-import IconUser from '@/assets/icons/icon-user.svg';
-import { Box, Text, TextErrors } from '@/components';
-import { useCunninghamTheme } from '@/cunningham';
-import { Role, Team } from '@/features/teams/';
-
-import { useDeleteTeamAccess } from '../api/useDeleteTeamAccess';
-import IconRemoveMember from '../assets/icon-remove-member.svg';
-import { useWhoAmI } from '../hooks/useWhoAmI';
-import { Access } from '../types';
-
-interface ModalDeleteProps {
- access: Access;
- currentRole: Role;
- onClose: () => void;
- team: Team;
-}
-
-export const ModalDelete = ({ access, onClose, team }: ModalDeleteProps) => {
- const { toast } = useToastProvider();
- const { colorsTokens } = useCunninghamTheme();
- const router = useRouter();
-
- const { isMyself, isLastOwner, isOtherOwner } = useWhoAmI(access);
- const isNotAllowed = isOtherOwner || isLastOwner;
-
- const {
- mutate: removeTeamAccess,
- error: errorUpdate,
- isError: isErrorUpdate,
- } = useDeleteTeamAccess({
- onSuccess: () => {
- toast(
- t('The member has been removed from the team'),
- VariantType.SUCCESS,
- {
- duration: 4000,
- },
- );
-
- // If we remove ourselves, we redirect to the home page
- // because we are no longer part of the team
- isMyself ? router.push('/') : onClose();
- },
- });
-
- return (
- onClose()}>
- {t('Cancel')}
-
- }
- onClose={onClose}
- rightActions={
-
- }
- size={ModalSize.MEDIUM}
- title={
-
-
-
- {t('Remove the member')}
-
-
- }
- >
-
-
- {t(
- 'Are you sure you want to remove this member from the {{team}} group?',
- { team: team.name },
- )}
-
-
- {isErrorUpdate && (
-
- )}
-
- {(isLastOwner || isOtherOwner) && (
-
- warning
- {isLastOwner &&
- t(
- 'You are the last owner, you cannot be removed from your team.',
- )}
- {isOtherOwner && t('You cannot remove other owner.')}
-
- )}
-
-
-
- {access.user.name}
-
-
-
- );
-};
diff --git a/src/frontend/apps/impress/src/features/members/components/ModalRole.tsx b/src/frontend/apps/impress/src/features/members/components/ModalRole.tsx
deleted file mode 100644
index 27f48e4a..00000000
--- a/src/frontend/apps/impress/src/features/members/components/ModalRole.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-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 { Role } from '@/features/teams';
-
-import { useUpdateTeamAccess } from '../api/useUpdateTeamAccess';
-import { useWhoAmI } from '../hooks/useWhoAmI';
-import { Access } from '../types';
-
-import { ChooseRole } from './ChooseRole';
-
-interface ModalRoleProps {
- access: Access;
- currentRole: Role;
- onClose: () => void;
- teamId: string;
-}
-
-export const ModalRole = ({
- access,
- currentRole,
- onClose,
- teamId,
-}: ModalRoleProps) => {
- const { t } = useTranslation();
- const [localRole, setLocalRole] = useState(access.role);
- const { toast } = useToastProvider();
- const {
- mutate: updateTeamAccess,
- error: errorUpdate,
- isError: isErrorUpdate,
- } = useUpdateTeamAccess({
- onSuccess: () => {
- toast(t('The role has been updated'), VariantType.SUCCESS, {
- duration: 4000,
- });
- onClose();
- },
- });
- const { isLastOwner, isOtherOwner } = useWhoAmI(access);
-
- const isNotAllowed = isOtherOwner || isLastOwner;
-
- return (
- onClose()}>
- {t('Cancel')}
-
- }
- onClose={() => onClose()}
- closeOnClickOutside
- hideCloseButton
- rightActions={
-
- }
- size={ModalSize.MEDIUM}
- title={t('Update the role')}
- >
-
- {isErrorUpdate && (
-
- )}
-
- {(isLastOwner || isOtherOwner) && (
-
- warning
- {isLastOwner &&
- t('You are the last owner, you cannot change your role.')}
- {isOtherOwner && t('You cannot update the role of other owner.')}
-
- )}
-
-
-
-
- );
-};
diff --git a/src/frontend/apps/impress/src/features/members/conf.ts b/src/frontend/apps/impress/src/features/members/conf.ts
deleted file mode 100644
index bfab9067..00000000
--- a/src/frontend/apps/impress/src/features/members/conf.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const PAGE_SIZE = 20;
diff --git a/src/frontend/apps/impress/src/features/members/hooks/useWhoAmI.tsx b/src/frontend/apps/impress/src/features/members/hooks/useWhoAmI.tsx
deleted file mode 100644
index 79eb5068..00000000
--- a/src/frontend/apps/impress/src/features/members/hooks/useWhoAmI.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useAuthStore } from '@/core/auth';
-import { Role } from '@/features/teams';
-
-import { Access } from '../types';
-
-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,
- };
-};
diff --git a/src/frontend/apps/impress/src/features/members/index.ts b/src/frontend/apps/impress/src/features/members/index.ts
deleted file mode 100644
index 4d6f0021..00000000
--- a/src/frontend/apps/impress/src/features/members/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from './api/useTeamsAccesses';
-export * from './components/ChooseRole';
-export * from './components/MemberGrid';
-export * from './types';
diff --git a/src/frontend/apps/impress/src/features/members/types.tsx b/src/frontend/apps/impress/src/features/members/types.tsx
deleted file mode 100644
index e8e858d2..00000000
--- a/src/frontend/apps/impress/src/features/members/types.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { User } from '@/core/auth';
-import { Role, Team } from '@/features/teams/';
-
-export interface Access {
- id: string;
- role: Role;
- user: User;
- abilities: {
- delete: boolean;
- get: boolean;
- patch: boolean;
- put: boolean;
- set_role_to: Role[];
- };
-}
-
-export interface Invitation {
- id: string;
- created_at: string;
- email: string;
- team: Team['id'];
- role: Role;
- issuer: User['id'];
- is_expired: boolean;
-}