diff --git a/src/frontend/apps/desk/src/features/teams/components/TeamActions.tsx b/src/frontend/apps/desk/src/features/teams/components/TeamActions.tsx new file mode 100644 index 0000000..d29c65a --- /dev/null +++ b/src/frontend/apps/desk/src/features/teams/components/TeamActions.tsx @@ -0,0 +1,78 @@ +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 '../types'; + +import { ModalRemoveTeam } from './ModalRemoveTeam'; +import { ModalUpdateTeam } from './ModalUpdateTeam'; + +interface TeamActionsProps { + currentRole: Role; + team: Team; +} + +export const TeamActions = ({ currentRole, team }: TeamActionsProps) => { + const { t } = useTranslation(); + const [isModalUpdateOpen, setIsModalUpdateOpen] = useState(false); + const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false); + const [isDropOpen, setIsDropOpen] = useState(false); + + if (currentRole === Role.MEMBER) { + return null; + } + + return ( + <> + + } + onOpenChange={(isOpen) => setIsDropOpen(isOpen)} + isOpen={isDropOpen} + > + + + {currentRole === Role.OWNER && ( + + )} + + + {isModalUpdateOpen && ( + setIsModalUpdateOpen(false)} + team={team} + /> + )} + {isModalRemoveOpen && ( + setIsModalRemoveOpen(false)} + team={team} + /> + )} + + ); +}; diff --git a/src/frontend/apps/desk/src/features/teams/components/TeamInfo.tsx b/src/frontend/apps/desk/src/features/teams/components/TeamInfo.tsx index 3fc504c..5dcb6a2 100644 --- a/src/frontend/apps/desk/src/features/teams/components/TeamInfo.tsx +++ b/src/frontend/apps/desk/src/features/teams/components/TeamInfo.tsx @@ -3,12 +3,13 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import IconGroup from '@/assets/icons/icon-group2.svg'; -import { Box, BoxButton, Card, IconOptions, Text } from '@/components'; +import { Box, Card, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Team } from '../api/types'; +import { Role, Team } from '../types'; import { ModalUpdateTeam } from './ModalUpdateTeam'; +import { TeamActions } from './TeamActions'; const format: DateTimeFormatOptions = { month: '2-digit', @@ -18,9 +19,10 @@ const format: DateTimeFormatOptions = { interface TeamInfoProps { team: Team; + currentRole: Role; } -export const TeamInfo = ({ team }: TeamInfoProps) => { +export const TeamInfo = ({ team, currentRole }: TeamInfoProps) => { const { t } = useTranslation(); const { colorsTokens } = useCunninghamTheme(); const { i18n } = useTranslation(); @@ -38,16 +40,7 @@ export const TeamInfo = ({ team }: TeamInfoProps) => { <> - { - setIsModalUpdateOpen(true); - }} - > - - + { return ( <> - + ); diff --git a/src/frontend/apps/e2e/__tests__/app-desk/common.ts b/src/frontend/apps/e2e/__tests__/app-desk/common.ts index d231814..ae77bcb 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/common.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/common.ts @@ -46,3 +46,44 @@ export const createTeam = async ( return randomTeams; }; + +export const addNewMember = async ( + page: Page, + index: number, + role: 'Admin' | 'Owner' | 'Member', + fillText: string = 'test', +) => { + const responsePromiseSearchUser = page.waitForResponse( + (response) => + response.url().includes(`/users/?q=${fillText}`) && + response.status() === 200, + ); + await page.getByLabel('Add members to the team').click(); + const inputSearch = page.getByLabel(/Find a member to add to the team/); + + // Select a new user + await inputSearch.fill(fillText); + + // Intercept response + const responseSearchUser = await responsePromiseSearchUser; + const users = (await responseSearchUser.json()).results as { + name: string; + }[]; + + // Choose user + await page.getByRole('option', { name: users[index].name }).click(); + + // Choose a role + await page.getByRole('radio', { name: role }).click(); + + await page.getByRole('button', { name: 'Validate' }).click(); + + const table = page.getByLabel('List members card').getByRole('table'); + + await expect(table.getByText(users[index].name)).toBeVisible(); + await expect( + page.getByText(`Member ${users[index].name} added to the team`), + ).toBeVisible(); + + return users[index].name; +}; diff --git a/src/frontend/apps/e2e/__tests__/app-desk/team.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/team.spec.ts index 6d287e9..60e4371 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/team.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/team.spec.ts @@ -44,7 +44,8 @@ test.describe('Team', () => { test('it updates the team name', async ({ page, browserName }) => { await createTeam(page, 'team-update-name', browserName, 1); - await page.getByLabel(`Open the team options modal`).click(); + await page.getByLabel(`Open the team options`).click(); + await page.getByRole('button', { name: `Update the team` }).click(); const teamName = randomName('new-team-update-name', browserName, 1)[0]; await page.getByText('New name...', { exact: true }).fill(teamName); diff --git a/src/frontend/apps/e2e/__tests__/app-desk/teams-delete.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/teams-delete.spec.ts new file mode 100644 index 0000000..65fbe34 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-desk/teams-delete.spec.ts @@ -0,0 +1,78 @@ +import { expect, test } from '@playwright/test'; + +import { addNewMember, createTeam, keyCloakSignIn } from './common'; + +test.beforeEach(async ({ page, browserName }) => { + await page.goto('/'); + await keyCloakSignIn(page, browserName); +}); + +test.describe('Teams Delete', () => { + test('it deletes the team when we are owner', async ({ + page, + browserName, + }) => { + await createTeam(page, 'team-update-name', browserName, 1); + + await page.getByLabel(`Open the team options`).click(); + await page.getByRole('button', { name: `Delete the team` }).click(); + await page.getByRole('button', { name: `Confirm deletion` }).click(); + await expect(page.getByText(`The team has been removed.`)).toBeVisible(); + await expect( + page.getByRole('button', { name: `Create a new team` }), + ).toBeVisible(); + }); + + test('it cannot delete the team when we are admin', async ({ + page, + browserName, + }) => { + await createTeam(page, 'team-update-name', browserName, 1); + + await addNewMember(page, 0, 'Owner'); + + // Change role to Admin + const table = page.getByLabel('List members card').getByRole('table'); + const myCells = table + .getByRole('row') + .filter({ hasText: new RegExp(`E2E ${browserName}`, 'i') }) + .getByRole('cell'); + await myCells.nth(4).getByLabel('Member options').click(); + + await page.getByText('Update the role').click(); + const radioGroup = page.getByLabel('Radio buttons to update the roles'); + await radioGroup.getByRole('radio', { name: 'Admin' }).click(); + await page.getByRole('button', { name: 'Validate' }).click(); + + // Delete the team button should be hidden + await page.getByLabel(`Open the team options`).click(); + await expect( + page.getByRole('button', { name: `Delete the team` }), + ).toBeHidden(); + }); + + test('it cannot delete the team when we are member', async ({ + page, + browserName, + }) => { + await createTeam(page, 'team-update-name', browserName, 1); + + await addNewMember(page, 0, 'Owner'); + + // Change role to Admin + const table = page.getByLabel('List members card').getByRole('table'); + const myCells = table + .getByRole('row') + .filter({ hasText: new RegExp(`E2E ${browserName}`, 'i') }) + .getByRole('cell'); + await myCells.nth(4).getByLabel('Member options').click(); + + await page.getByText('Update the role').click(); + const radioGroup = page.getByLabel('Radio buttons to update the roles'); + await radioGroup.getByRole('radio', { name: 'Member' }).click(); + await page.getByRole('button', { name: 'Validate' }).click(); + + // Option button should be hidden + await expect(page.getByLabel(`Open the team options`)).toBeHidden(); + }); +}); diff --git a/src/frontend/packages/eslint-config-people/playwright.js b/src/frontend/packages/eslint-config-people/playwright.js index ae963e2..0bbfdf9 100644 --- a/src/frontend/packages/eslint-config-people/playwright.js +++ b/src/frontend/packages/eslint-config-people/playwright.js @@ -15,6 +15,12 @@ module.exports = { rules: { ...common.globalRules, '@next/next/no-html-link-for-pages': 'off' }, overrides: [ ...common.eslintTS, + { + files: ['**/*.ts'], + rules: { + '@typescript-eslint/no-unsafe-member-access': 'off', + }, + }, { files: ['*.spec.*', '*.test.*', '**/__mock__/**/*'], extends: ['plugin:playwright/recommended'],