diff --git a/src/frontend/apps/desk/src/features/teams/assets/icon-edit.svg b/src/frontend/apps/desk/src/features/teams/assets/icon-edit.svg new file mode 100644 index 0000000..5e31621 --- /dev/null +++ b/src/frontend/apps/desk/src/features/teams/assets/icon-edit.svg @@ -0,0 +1,6 @@ + + + diff --git a/src/frontend/apps/desk/src/features/teams/components/ModalUpdateTeam.tsx b/src/frontend/apps/desk/src/features/teams/components/ModalUpdateTeam.tsx new file mode 100644 index 0000000..529270e --- /dev/null +++ b/src/frontend/apps/desk/src/features/teams/components/ModalUpdateTeam.tsx @@ -0,0 +1,117 @@ +import { + Button, + Input, + Loader, + Modal, + ModalSize, + VariantType, + useToastProvider, +} from '@openfun/cunningham-react'; +import { t } from 'i18next'; +import { useEffect, useState } from 'react'; + +import { Box, Text, TextErrors } from '@/components'; +import useCunninghamTheme from '@/cunningham/useCunninghamTheme'; + +import { Team, useUpdateTeam } from '../api'; +import IconEdit from '../assets/icon-edit.svg'; + +interface ModalUpdateTeamProps { + onClose: () => void; + team: Team; +} + +export const ModalUpdateTeam = ({ onClose, team }: ModalUpdateTeamProps) => { + const { colorsTokens } = useCunninghamTheme(); + const [newTeamName, setNewTeamName] = useState(team.name); + const [isShowingError, setIsShowingError] = useState(false); + const { toast } = useToastProvider(); + + const { + mutate: updateTeam, + isError, + isPending, + error, + } = useUpdateTeam({ + onSuccess: () => { + toast(t('The team has been updated.'), VariantType.SUCCESS, { + duration: 4000, + }); + onClose(); + }, + }); + + useEffect(() => { + if (isError) { + setIsShowingError(true); + } + }, [isError]); + + return ( + onClose()} + > + {t('Cancel')} + + } + onClose={() => onClose()} + rightActions={ + + } + size={ModalSize.MEDIUM} + title={ + + + + {t('Update team {{teamName}}', { teamName: team.name })} + + + } + > + + + {t('Enter the new name of the selected team')} + + + { + setNewTeamName(e.target.value); + setIsShowingError(false); + }} + rightIcon={edit} + state={isShowingError ? 'error' : undefined} + /> + {isError && error && } + {isPending && ( + + + + )} + + + ); +}; 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 0b1cffd..3fc504c 100644 --- a/src/frontend/apps/desk/src/features/teams/components/TeamInfo.tsx +++ b/src/frontend/apps/desk/src/features/teams/components/TeamInfo.tsx @@ -1,13 +1,15 @@ import { DateTime, DateTimeFormatOptions } from 'luxon'; -import React from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import IconGroup from '@/assets/icons/icon-group2.svg'; -import { Box, Card, Text } from '@/components'; +import { Box, BoxButton, Card, IconOptions, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { Team } from '../api/types'; +import { ModalUpdateTeam } from './ModalUpdateTeam'; + const format: DateTimeFormatOptions = { month: '2-digit', day: '2-digit', @@ -22,6 +24,7 @@ export const TeamInfo = ({ team }: TeamInfoProps) => { const { t } = useTranslation(); const { colorsTokens } = useCunninghamTheme(); const { i18n } = useTranslation(); + const [isModalUpdateOpen, setIsModalUpdateOpen] = useState(false); const created_at = DateTime.fromISO(team.created_at) .setLocale(i18n.language) @@ -32,58 +35,79 @@ export const TeamInfo = ({ team }: TeamInfoProps) => { .toLocaleString(format); return ( - - - - - - {t('Members of “{{teamName}}“', { - teamName: team.name, - })} + <> + + + { + setIsModalUpdateOpen(true); + }} + > + + + + + + + + {t('Members of “{{teamName}}“', { + teamName: team.name, + })} + + + {t('Add people to the “{{teamName}}“ group.', { + teamName: team.name, + })} + + + + + + {t('{{count}} member', { count: team.accesses.length })} - - {t('Add people to the “{{teamName}}“ group.', { - teamName: team.name, - })} + + {t('Created at')}  + + {created_at} + + + + {t('Last update at')}  + + {updated_at} + - - - - {t('{{count}} member', { count: team.accesses.length })} - - - {t('Created at')}  - - {created_at} - - - - {t('Last update at')}  - - {updated_at} - - - - + + {isModalUpdateOpen && ( + setIsModalUpdateOpen(false)} + team={team} + /> + )} + ); }; diff --git a/src/frontend/apps/desk/src/i18n/translations.json b/src/frontend/apps/desk/src/i18n/translations.json index 5b07949..9e77fd4 100644 --- a/src/frontend/apps/desk/src/i18n/translations.json +++ b/src/frontend/apps/desk/src/i18n/translations.json @@ -13,7 +13,9 @@ "Cancel": "Annuler", "Cells icon": "Icône Cellules", "Choose a role": "Choisissez un rôle", + "Close the modal": "Fermer la modale", "Contacts": "Contacts", + "Content modal to update the team": "Contenu modal pour mettre à jour le groupe", "Create a new team": "Créer un nouveau groupe", "Create new team card": "Carte créer une nouvelle équipe", "Create the team": "Créer le groupe", @@ -23,6 +25,8 @@ "Desk Logo": "Logo Desk", "Emails": "Emails", "Empty teams icon": "Icône de groupe vide", + "Enter the new name of the selected team": "Entrez le nouveau nom du groupe sélectionné", + "Enter the new team name": "Entrez le nouveau nom de groupe", "Favorite": "Favoris", "Find a member to add to the team": "Trouver un membre à ajouter au groupe", "Freedom Equality Fraternity Logo": "Logo Liberté Égalité Fraternité", @@ -38,7 +42,9 @@ "Members of “{{teamName}}“": "Membres de “{{teamName}}“", "Name the team": "Nommer le groupe", "Names": "Noms", + "New name...": "Nouveau nom...", "Open the member options modal": "Ouvrir les options de membre dans la fenêtre modale", + "Open the team options modal": "Ouvrir les options de groupe dans la fenêtre modale", "Owner": "Propriétaire", "People": "People", "People Description": "Description de People", @@ -56,8 +62,11 @@ "Team name": "Nom du groupe", "Teams icon": "Icône de groupe", "The role has been updated": "Le rôle a bien été mis à jour", + "The team has been updated.": "Le groupe a été mise à jour.", + "Update team {{teamName}}": "Modification du groupe {{teamName}}", "Update the role": "Mettre à jour ce rôle", "Validate": "Valider", + "Validate the modification": "Valider la modification", "You are the last owner, you cannot change your role.": "Vous êtes le dernier propriétaire, vous ne pouvez pas changer votre rôle.", "You cannot update the role of other owner.": "Vous ne pouvez pas mettre à jour les rôles d'autre propriétaire.", "icon group": "icône groupe", diff --git a/src/frontend/apps/e2e/__tests__/app-desk/common.ts b/src/frontend/apps/e2e/__tests__/app-desk/common.ts index bc34179..08203d0 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/common.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/common.ts @@ -18,6 +18,15 @@ export const keyCloakSignIn = async (page: Page, browserName: string) => { } }; +export const randomTeamsName = ( + teamName: string, + browserName: string, + length: number, +) => + Array.from({ length }, (_el, index) => { + return `${teamName}-${browserName}-${Math.floor(Math.random() * 10000)}-${index}`; + }); + export const createTeam = async ( page: Page, teamName: string, @@ -27,9 +36,7 @@ export const createTeam = async ( const panel = page.getByLabel('Teams panel').first(); const buttonCreate = page.getByRole('button', { name: 'Create the team' }); - const randomTeams = Array.from({ length }, (_el, index) => { - return `${teamName}-${browserName}-${Math.floor(Math.random() * 10000)}-${index}`; - }); + const randomTeams = randomTeamsName(teamName, browserName, length); for (let i = 0; i < randomTeams.length; i++) { await panel.getByRole('button', { name: 'Add a team' }).click(); 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 b732251..fd3370f 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/team.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/team.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { createTeam, keyCloakSignIn } from './common'; +import { createTeam, keyCloakSignIn, randomTeamsName } from './common'; test.beforeEach(async ({ page, browserName }) => { await page.goto('/'); @@ -40,4 +40,22 @@ test.describe('Team', () => { page.getByText(`Last update at ${todayFormated}`), ).toBeVisible(); }); + + 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(); + + const teamName = randomTeamsName('new-team-update-name', browserName, 1)[0]; + await page.getByText('New name...', { exact: true }).fill(teamName); + + await page + .getByRole('button', { name: 'Validate the modification' }) + .click(); + + await expect(page.getByText('The team has been updated.')).toBeVisible(); + await expect( + page.getByText(`Add people to the “${teamName}“ group.`), + ).toBeVisible(); + }); });