✨(app-desk) modal update team
Integrate the modal and the logic to update a team.
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M6 34.5002V42.0002H13.5L35.62 19.8802L28.12 12.3802L6 34.5002ZM42.82 12.6802L35.32 5.18018L30.26 10.2602L37.76 17.7602L42.82 12.6802Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 259 B |
@@ -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 (
|
||||||
|
<Modal
|
||||||
|
isOpen
|
||||||
|
closeOnClickOutside
|
||||||
|
hideCloseButton
|
||||||
|
leftActions={
|
||||||
|
<Button
|
||||||
|
aria-label={t('Close the modal')}
|
||||||
|
color="secondary"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => onClose()}
|
||||||
|
>
|
||||||
|
{t('Cancel')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
onClose={() => onClose()}
|
||||||
|
rightActions={
|
||||||
|
<Button
|
||||||
|
aria-label={t('Validate the modification')}
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
onClick={() =>
|
||||||
|
updateTeam({
|
||||||
|
name: newTeamName,
|
||||||
|
id: team.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('Validate the modification')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size={ModalSize.MEDIUM}
|
||||||
|
title={
|
||||||
|
<Box $align="center" $gap="1rem">
|
||||||
|
<IconEdit width={48} color={colorsTokens()['primary-text']} />
|
||||||
|
<Text $size="h3" className="m-0">
|
||||||
|
{t('Update team {{teamName}}', { teamName: team.name })}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Box className="mb-xl" aria-label={t('Content modal to update the team')}>
|
||||||
|
<Text as="p" className="mb-b">
|
||||||
|
{t('Enter the new name of the selected team')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
fullWidth
|
||||||
|
type="text"
|
||||||
|
label={t('New name...')}
|
||||||
|
defaultValue={team.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNewTeamName(e.target.value);
|
||||||
|
setIsShowingError(false);
|
||||||
|
}}
|
||||||
|
rightIcon={<span className="material-icons">edit</span>}
|
||||||
|
state={isShowingError ? 'error' : undefined}
|
||||||
|
/>
|
||||||
|
{isError && error && <TextErrors causes={error.cause} />}
|
||||||
|
{isPending && (
|
||||||
|
<Box $align="center">
|
||||||
|
<Loader />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import { DateTime, DateTimeFormatOptions } from 'luxon';
|
import { DateTime, DateTimeFormatOptions } from 'luxon';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import IconGroup from '@/assets/icons/icon-group2.svg';
|
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 { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
import { Team } from '../api/types';
|
import { Team } from '../api/types';
|
||||||
|
|
||||||
|
import { ModalUpdateTeam } from './ModalUpdateTeam';
|
||||||
|
|
||||||
const format: DateTimeFormatOptions = {
|
const format: DateTimeFormatOptions = {
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@@ -22,6 +24,7 @@ export const TeamInfo = ({ team }: TeamInfoProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
const [isModalUpdateOpen, setIsModalUpdateOpen] = useState(false);
|
||||||
|
|
||||||
const created_at = DateTime.fromISO(team.created_at)
|
const created_at = DateTime.fromISO(team.created_at)
|
||||||
.setLocale(i18n.language)
|
.setLocale(i18n.language)
|
||||||
@@ -32,58 +35,79 @@ export const TeamInfo = ({ team }: TeamInfoProps) => {
|
|||||||
.toLocaleString(format);
|
.toLocaleString(format);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="m-b" style={{ paddingBottom: 0 }}>
|
<>
|
||||||
<Box className="m-b" $direction="row" $align="center" $gap="1.5rem">
|
<Card className="m-b" style={{ paddingBottom: 0 }}>
|
||||||
<IconGroup
|
<Box $css="align-self: flex-end;" className="m-t" $position="absolute">
|
||||||
width={44}
|
<BoxButton
|
||||||
color={colorsTokens()['primary-text']}
|
onClick={() => {
|
||||||
aria-label={t('icon group')}
|
setIsModalUpdateOpen(true);
|
||||||
style={{
|
}}
|
||||||
flexShrink: 0,
|
>
|
||||||
alignSelf: 'start',
|
<IconOptions
|
||||||
}}
|
isOpen={isModalUpdateOpen}
|
||||||
/>
|
aria-label={t('Open the team options modal')}
|
||||||
<Box>
|
/>
|
||||||
<Text as="h3" $weight="bold" $size="1.25rem" className="mt-0">
|
</BoxButton>
|
||||||
{t('Members of “{{teamName}}“', {
|
</Box>
|
||||||
teamName: team.name,
|
<Box className="m-b" $direction="row" $align="center" $gap="1.5rem">
|
||||||
})}
|
<IconGroup
|
||||||
|
width={44}
|
||||||
|
color={colorsTokens()['primary-text']}
|
||||||
|
aria-label={t('icon group')}
|
||||||
|
style={{
|
||||||
|
flexShrink: 0,
|
||||||
|
alignSelf: 'start',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text as="h3" $weight="bold" $size="1.25rem" className="mt-0">
|
||||||
|
{t('Members of “{{teamName}}“', {
|
||||||
|
teamName: team.name,
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
<Text $size="m">
|
||||||
|
{t('Add people to the “{{teamName}}“ group.', {
|
||||||
|
teamName: team.name,
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
className="p-s"
|
||||||
|
$gap="2rem"
|
||||||
|
$direction="row"
|
||||||
|
$justify="start"
|
||||||
|
$css={`
|
||||||
|
border-top: 1px solid ${colorsTokens()['card-border']};
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
padding-left: 6rem;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Text $size="s" as="p">
|
||||||
|
{t('{{count}} member', { count: team.accesses.length })}
|
||||||
</Text>
|
</Text>
|
||||||
<Text $size="m">
|
<Text $size="s" $display="inline" as="p">
|
||||||
{t('Add people to the “{{teamName}}“ group.', {
|
{t('Created at')}
|
||||||
teamName: team.name,
|
<Text $weight="bold" $display="inline">
|
||||||
})}
|
{created_at}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text $size="s" $display="inline" as="p">
|
||||||
|
{t('Last update at')}
|
||||||
|
<Text $weight="bold" $display="inline">
|
||||||
|
{updated_at}
|
||||||
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Card>
|
||||||
<Box
|
{isModalUpdateOpen && (
|
||||||
className="p-s"
|
<ModalUpdateTeam
|
||||||
$gap="2rem"
|
onClose={() => setIsModalUpdateOpen(false)}
|
||||||
$direction="row"
|
team={team}
|
||||||
$justify="start"
|
/>
|
||||||
$css={`
|
)}
|
||||||
border-top: 1px solid ${colorsTokens()['card-border']};
|
</>
|
||||||
padding-left: 1.5rem;
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
padding-left: 6rem;
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Text $size="s" as="p">
|
|
||||||
{t('{{count}} member', { count: team.accesses.length })}
|
|
||||||
</Text>
|
|
||||||
<Text $size="s" $display="inline" as="p">
|
|
||||||
{t('Created at')}
|
|
||||||
<Text $weight="bold" $display="inline">
|
|
||||||
{created_at}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text $size="s" $display="inline" as="p">
|
|
||||||
{t('Last update at')}
|
|
||||||
<Text $weight="bold" $display="inline">
|
|
||||||
{updated_at}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
"Cancel": "Annuler",
|
"Cancel": "Annuler",
|
||||||
"Cells icon": "Icône Cellules",
|
"Cells icon": "Icône Cellules",
|
||||||
"Choose a role": "Choisissez un rôle",
|
"Choose a role": "Choisissez un rôle",
|
||||||
|
"Close the modal": "Fermer la modale",
|
||||||
"Contacts": "Contacts",
|
"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 a new team": "Créer un nouveau groupe",
|
||||||
"Create new team card": "Carte créer une nouvelle équipe",
|
"Create new team card": "Carte créer une nouvelle équipe",
|
||||||
"Create the team": "Créer le groupe",
|
"Create the team": "Créer le groupe",
|
||||||
@@ -23,6 +25,8 @@
|
|||||||
"Desk Logo": "Logo Desk",
|
"Desk Logo": "Logo Desk",
|
||||||
"Emails": "Emails",
|
"Emails": "Emails",
|
||||||
"Empty teams icon": "Icône de groupe vide",
|
"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",
|
"Favorite": "Favoris",
|
||||||
"Find a member to add to the team": "Trouver un membre à ajouter au groupe",
|
"Find a member to add to the team": "Trouver un membre à ajouter au groupe",
|
||||||
"Freedom Equality Fraternity Logo": "Logo Liberté Égalité Fraternité",
|
"Freedom Equality Fraternity Logo": "Logo Liberté Égalité Fraternité",
|
||||||
@@ -38,7 +42,9 @@
|
|||||||
"Members of “{{teamName}}“": "Membres de “{{teamName}}“",
|
"Members of “{{teamName}}“": "Membres de “{{teamName}}“",
|
||||||
"Name the team": "Nommer le groupe",
|
"Name the team": "Nommer le groupe",
|
||||||
"Names": "Noms",
|
"Names": "Noms",
|
||||||
|
"New name...": "Nouveau nom...",
|
||||||
"Open the member options modal": "Ouvrir les options de membre dans la fenêtre modale",
|
"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",
|
"Owner": "Propriétaire",
|
||||||
"People": "People",
|
"People": "People",
|
||||||
"People Description": "Description de People",
|
"People Description": "Description de People",
|
||||||
@@ -56,8 +62,11 @@
|
|||||||
"Team name": "Nom du groupe",
|
"Team name": "Nom du groupe",
|
||||||
"Teams icon": "Icône de groupe",
|
"Teams icon": "Icône de groupe",
|
||||||
"The role has been updated": "Le rôle a bien été mis à jour",
|
"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",
|
"Update the role": "Mettre à jour ce rôle",
|
||||||
"Validate": "Valider",
|
"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 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.",
|
"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",
|
"icon group": "icône groupe",
|
||||||
|
|||||||
@@ -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 (
|
export const createTeam = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
teamName: string,
|
teamName: string,
|
||||||
@@ -27,9 +36,7 @@ export const createTeam = async (
|
|||||||
const panel = page.getByLabel('Teams panel').first();
|
const panel = page.getByLabel('Teams panel').first();
|
||||||
const buttonCreate = page.getByRole('button', { name: 'Create the team' });
|
const buttonCreate = page.getByRole('button', { name: 'Create the team' });
|
||||||
|
|
||||||
const randomTeams = Array.from({ length }, (_el, index) => {
|
const randomTeams = randomTeamsName(teamName, browserName, length);
|
||||||
return `${teamName}-${browserName}-${Math.floor(Math.random() * 10000)}-${index}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < randomTeams.length; i++) {
|
for (let i = 0; i < randomTeams.length; i++) {
|
||||||
await panel.getByRole('button', { name: 'Add a team' }).click();
|
await panel.getByRole('button', { name: 'Add a team' }).click();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { createTeam, keyCloakSignIn } from './common';
|
import { createTeam, keyCloakSignIn, randomTeamsName } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page, browserName }) => {
|
test.beforeEach(async ({ page, browserName }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -40,4 +40,22 @@ test.describe('Team', () => {
|
|||||||
page.getByText(`Last update at ${todayFormated}`),
|
page.getByText(`Last update at ${todayFormated}`),
|
||||||
).toBeVisible();
|
).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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user