✨(app-desk) add team page
- add the team page, you can access to the team page with the id of the team. - Add link to the panel team to access to the team page.
This commit is contained in:
@@ -12,7 +12,7 @@ export interface CreateTeamResponseError {
|
|||||||
detail: string;
|
detail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTeam = async (name: string) => {
|
export const createTeam = async (name: string): Promise<CreateTeamResponse> => {
|
||||||
const response = await fetchAPI(`teams/`, {
|
const response = await fetchAPI(`teams/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -24,17 +24,22 @@ export const createTeam = async (name: string) => {
|
|||||||
throw new Error(`Couldn't create team: ${response.statusText}`);
|
throw new Error(`Couldn't create team: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json() as Promise<CreateTeamResponse>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useCreateTeam() {
|
interface CreateTeamProps {
|
||||||
|
onSuccess: (data: CreateTeamResponse) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreateTeam({ onSuccess }: CreateTeamProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<CreateTeamResponse, CreateTeamResponseError, string>({
|
return useMutation<CreateTeamResponse, CreateTeamResponseError, string>({
|
||||||
mutationFn: createTeam,
|
mutationFn: createTeam,
|
||||||
onSuccess: () => {
|
onSuccess: (data) => {
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [KEY_LIST_TEAM],
|
queryKey: [KEY_LIST_TEAM],
|
||||||
});
|
});
|
||||||
|
onSuccess(data);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import IconGroup from '@/assets/icons/icon-group.svg';
|
import IconGroup from '@/assets/icons/icon-group.svg';
|
||||||
import { Box, Text } from '@/components';
|
import { Box, StyledLink, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
import { TeamResponse } from '../api/useTeams';
|
import { TeamResponse } from '../api/types';
|
||||||
import IconNone from '../assets/icon-none.svg';
|
import IconNone from '../assets/icon-none.svg';
|
||||||
|
|
||||||
interface TeamProps {
|
interface TeamProps {
|
||||||
@@ -29,29 +29,33 @@ export const PanelTeam = ({ team }: TeamProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box as="li" $direction="row" $align="center" $gap="0.5rem">
|
<Box as="li">
|
||||||
{hasMembers ? (
|
<StyledLink href={`/teams/${team.id}`}>
|
||||||
<IconGroup
|
<Box $align="center" $direction="row" $gap="0.5rem">
|
||||||
aria-label={t(`Teams icon`)}
|
{hasMembers ? (
|
||||||
color={colorsTokens()['primary-500']}
|
<IconGroup
|
||||||
{...commonProps}
|
aria-label={t(`Teams icon`)}
|
||||||
style={{
|
color={colorsTokens()['primary-500']}
|
||||||
...commonProps.style,
|
{...commonProps}
|
||||||
border: `1px solid ${colorsTokens()['primary-300']}`,
|
style={{
|
||||||
}}
|
...commonProps.style,
|
||||||
/>
|
border: `1px solid ${colorsTokens()['primary-300']}`,
|
||||||
) : (
|
}}
|
||||||
<IconNone
|
/>
|
||||||
aria-label={t(`Empty teams icon`)}
|
) : (
|
||||||
color={colorsTokens()['greyscale-500']}
|
<IconNone
|
||||||
{...commonProps}
|
aria-label={t(`Empty teams icon`)}
|
||||||
style={{
|
color={colorsTokens()['greyscale-500']}
|
||||||
...commonProps.style,
|
{...commonProps}
|
||||||
border: `1px solid ${colorsTokens()['greyscale-300']}`,
|
style={{
|
||||||
}}
|
...commonProps.style,
|
||||||
/>
|
border: `1px solid ${colorsTokens()['greyscale-300']}`,
|
||||||
)}
|
}}
|
||||||
<Text $weight="bold">{team.name}</Text>
|
/>
|
||||||
|
)}
|
||||||
|
<Text $weight="bold">{team.name}</Text>
|
||||||
|
</Box>
|
||||||
|
</StyledLink>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
65
src/frontend/apps/desk/src/pages/teams/[id].tsx
Normal file
65
src/frontend/apps/desk/src/pages/teams/[id].tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { Loader } from '@openfun/cunningham-react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { Box, Text } from '@/components';
|
||||||
|
import { useTeam } from '@/features/teams/api/useTeam';
|
||||||
|
import { NextPageWithLayout } from '@/types/next';
|
||||||
|
|
||||||
|
import TeamLayout from './TeamLayout';
|
||||||
|
|
||||||
|
const Page: NextPageWithLayout = () => {
|
||||||
|
const {
|
||||||
|
query: { id },
|
||||||
|
} = useRouter();
|
||||||
|
|
||||||
|
if (typeof id !== 'string') {
|
||||||
|
throw new Error('Invalid team id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Team id={id} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TeamProps {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Team = ({ id }: TeamProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: team, isLoading, isError } = useTeam({ id });
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
$align="center"
|
||||||
|
$justify="center"
|
||||||
|
$height="100%"
|
||||||
|
$theme="danger"
|
||||||
|
$textAlign="center"
|
||||||
|
>
|
||||||
|
{t('Something bad happens, please retry.')}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Box $align="center" $justify="center" $height="100%">
|
||||||
|
<Loader />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text as="h3" $textAlign="center">
|
||||||
|
Teams: {team?.name}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Page.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <TeamLayout>{page}</TeamLayout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Button, Input, Loader } from '@openfun/cunningham-react';
|
import { Button, Input, Loader } from '@openfun/cunningham-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { ReactElement, useState } from 'react';
|
import { ReactElement, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -12,7 +13,16 @@ import TeamLayout from './TeamLayout';
|
|||||||
|
|
||||||
const Page: NextPageWithLayout = () => {
|
const Page: NextPageWithLayout = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { mutate: createTeam, isError, isPending } = useCreateTeam();
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
mutate: createTeam,
|
||||||
|
isError,
|
||||||
|
isPending,
|
||||||
|
} = useCreateTeam({
|
||||||
|
onSuccess: (team) => {
|
||||||
|
router.push(`/teams/${team.id}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
const [teamName, setTeamName] = useState('');
|
const [teamName, setTeamName] = useState('');
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
|
|||||||
@@ -58,4 +58,26 @@ test.describe('Teams', () => {
|
|||||||
|
|
||||||
await expect(buttonCreateHomepage).toBeVisible();
|
await expect(buttonCreateHomepage).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('checks the routing on new team created', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
const panel = page.getByLabel('Teams panel').first();
|
||||||
|
|
||||||
|
await panel.getByRole('button', { name: 'Add a team' }).click();
|
||||||
|
|
||||||
|
const teamName = `My routing team ${browserName}-${Math.floor(Math.random() * 1000)}`;
|
||||||
|
await page.getByText('Team name').fill(teamName);
|
||||||
|
await page.getByRole('button', { name: 'Create the team' }).click();
|
||||||
|
|
||||||
|
const elTeam = page.getByText(`Teams: ${teamName}`);
|
||||||
|
await expect(elTeam).toBeVisible();
|
||||||
|
|
||||||
|
await panel.getByRole('button', { name: 'Add a team' }).click();
|
||||||
|
await expect(elTeam).toBeHidden();
|
||||||
|
|
||||||
|
await panel.locator('li').getByText(teamName).click();
|
||||||
|
await expect(elTeam).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,28 +27,23 @@ test.describe('Teams Panel', () => {
|
|||||||
name: 'Add a team',
|
name: 'Add a team',
|
||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await expect(
|
|
||||||
panel.getByText(
|
|
||||||
'Create your first team by clicking on the "Create a new team" button.',
|
|
||||||
),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('002 - checks the sort button', async ({ page, browserName }) => {
|
test('002 - checks the sort button', async ({ page, browserName }) => {
|
||||||
const panel = page.getByLabel('Teams panel').first();
|
const panel = page.getByLabel('Teams panel').first();
|
||||||
|
|
||||||
await panel.getByRole('button', { name: 'Add a team' }).click();
|
const buttonCreate = page.getByRole('button', { name: 'Create the team' });
|
||||||
|
const randomTeams = Array.from({ length: 3 }, (_el, index) => {
|
||||||
const randomTeams = Array.from({ length: 3 }, () => {
|
return `team-sort-${browserName}-${Math.floor(Math.random() * 1000)}-${index}`;
|
||||||
return `team-sort-${browserName}-${Math.floor(Math.random() * 1000)}`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < randomTeams.length; i++) {
|
||||||
await page.getByText('Team name').fill(`${randomTeams[i]}-${i}`);
|
await panel.getByRole('button', { name: 'Add a team' }).click();
|
||||||
await page.getByRole('button', { name: 'Create the team' }).click();
|
await page.getByText('Team name').fill(randomTeams[i]);
|
||||||
|
await expect(buttonCreate).toBeEnabled();
|
||||||
|
await buttonCreate.click();
|
||||||
await expect(
|
await expect(
|
||||||
panel.locator('li').nth(0).getByText(`${randomTeams[i]}-${i}`),
|
panel.locator('li').nth(0).getByText(randomTeams[i]),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,32 +53,33 @@ test.describe('Teams Panel', () => {
|
|||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
await expect(panel.locator('li').getByText(randomTeams[1])).toBeVisible();
|
||||||
await expect(
|
|
||||||
panel.locator('li').nth(i).getByText(`${randomTeams[i]}-${i}`),
|
const allTeams = await panel.locator('li').allTextContents();
|
||||||
).toBeVisible();
|
const sortedTeamTexts = allTeams.filter((team) =>
|
||||||
}
|
randomTeams.some((randomTeam) => team.includes(randomTeam)),
|
||||||
|
);
|
||||||
|
expect(sortedTeamTexts).toStrictEqual(randomTeams);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('003 - checks the infinite scrool', async ({ page, browserName }) => {
|
test('003 - checks the infinite scrool', async ({ page, browserName }) => {
|
||||||
test.setTimeout(90000);
|
test.setTimeout(90000);
|
||||||
const panel = page.getByLabel('Teams panel').first();
|
const panel = page.getByLabel('Teams panel').first();
|
||||||
|
|
||||||
await panel.getByRole('button', { name: 'Add a team' }).click();
|
const buttonCreate = page.getByRole('button', { name: 'Create the team' });
|
||||||
|
const randomTeams = Array.from({ length: 40 }, (_el, index) => {
|
||||||
const randomTeams = Array.from({ length: 40 }, () => {
|
return `team-infinite-${browserName}-${Math.floor(Math.random() * 10000)}-${index}`;
|
||||||
return `team-infinite-${browserName}-${Math.floor(Math.random() * 10000)}`;
|
|
||||||
});
|
});
|
||||||
for (let i = 0; i < 40; i++) {
|
for (let i = 0; i < randomTeams.length; i++) {
|
||||||
await page.getByText('Team name').fill(`${randomTeams[i]}-${i}`);
|
await panel.getByRole('button', { name: 'Add a team' }).click();
|
||||||
await page.getByRole('button', { name: 'Create the team' }).click();
|
await page.getByText('Team name').fill(randomTeams[i]);
|
||||||
await expect(
|
await expect(buttonCreate).toBeEnabled();
|
||||||
panel.locator('li').getByText(`${randomTeams[i]}-${i}`),
|
await buttonCreate.click();
|
||||||
).toBeVisible();
|
await expect(panel.locator('li').getByText(randomTeams[i])).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(panel.locator('li')).toHaveCount(20);
|
await expect(panel.locator('li')).toHaveCount(20);
|
||||||
await panel.getByText(`${randomTeams[24]}-${24}`).click();
|
await panel.getByText(randomTeams[24]).click();
|
||||||
|
|
||||||
await waitForElementCount(panel.locator('li'), 21, 10000);
|
await waitForElementCount(panel.locator('li'), 21, 10000);
|
||||||
expect(await panel.locator('li').count()).toBeGreaterThan(20);
|
expect(await panel.locator('li').count()).toBeGreaterThan(20);
|
||||||
|
|||||||
Reference in New Issue
Block a user