diff --git a/frontend/src/components/Teams/TeamsList/TeamItem/TeamItem.spec.tsx b/frontend/src/components/Teams/TeamsList/TeamItem/TeamItem.spec.tsx
index 29bd3f4b1..928247b82 100644
--- a/frontend/src/components/Teams/TeamsList/TeamItem/TeamItem.spec.tsx
+++ b/frontend/src/components/Teams/TeamsList/TeamItem/TeamItem.spec.tsx
@@ -8,7 +8,6 @@ import { TeamUserRoles } from '@/utils/enums/team.user.roles';
import TeamItem, { TeamItemProps } from './index';
const DEFAULT_PROPS = {
- userId: '',
team: TeamFactory.create(),
};
@@ -18,8 +17,13 @@ jest.mock('next/router', () => ({
useRouter: () => router,
}));
-const render = (props: TeamItemProps = DEFAULT_PROPS) =>
- renderWithProviders(, { routerOptions: router });
+const render = (props: TeamItemProps = DEFAULT_PROPS, options?: any) =>
+ renderWithProviders(, {
+ routerOptions: router,
+ sessionOptions: {
+ user: options?.user,
+ },
+ });
describe('Components/TeamItem', () => {
let testProps: TeamItemProps;
@@ -32,10 +36,9 @@ describe('Components/TeamItem', () => {
const teamItemProps = { ...testProps };
// Act
- const { getByTestId, getByText } = render(teamItemProps);
+ const { getByText } = render(teamItemProps);
// Assert
- expect(getByTestId('teamitemTitle')).toBeInTheDocument();
expect(getByText(teamItemProps.team.name)).toBeInTheDocument();
});
@@ -50,11 +53,10 @@ describe('Components/TeamItem', () => {
};
// Act
- const { getByTestId } = render(teamItemProps);
+ const { getByText } = render(teamItemProps);
// Assert
- expect(getByTestId('teamitemBoards')).toBeInTheDocument();
- expect(getByTestId('teamitemBoards')).toHaveTextContent('3 team boards');
+ expect(getByText('3 team boards')).toBeInTheDocument();
});
it('should render no team boards', () => {
@@ -68,18 +70,16 @@ describe('Components/TeamItem', () => {
};
// Act
- const { getByTestId } = render(teamItemProps);
+ const { getByText } = render(teamItemProps);
// Assert
- expect(getByTestId('teamitemBoards')).toBeInTheDocument();
- expect(getByTestId('teamitemBoards')).toHaveTextContent('No boards');
+ expect(getByText('No boards')).toBeInTheDocument();
});
it('should render create first board', () => {
// Arrange
const teamAdmin = TeamUserFactory.create({ role: TeamUserRoles.ADMIN });
const teamItemProps = {
- userId: teamAdmin.user._id,
team: {
...testProps.team,
boardsCount: 0,
@@ -88,10 +88,9 @@ describe('Components/TeamItem', () => {
};
// Act
- const { getByTestId } = render(teamItemProps);
+ const { getByText } = render(teamItemProps, { user: teamAdmin.user });
// Assert
- expect(getByTestId('teamitemBoards')).toBeInTheDocument();
- expect(getByTestId('teamitemBoards')).toHaveTextContent('Create first board');
+ expect(getByText('Create first board')).toBeInTheDocument();
});
});
diff --git a/frontend/src/components/Teams/TeamsList/TeamItem/index.tsx b/frontend/src/components/Teams/TeamsList/TeamItem/index.tsx
index ffba62920..daa91ce78 100644
--- a/frontend/src/components/Teams/TeamsList/TeamItem/index.tsx
+++ b/frontend/src/components/Teams/TeamsList/TeamItem/index.tsx
@@ -30,18 +30,15 @@ const InnerContainer = styled(Flex, Box, {
});
export type TeamItemProps = {
- userId: string | undefined;
team: Team;
isTeamPage?: boolean;
};
-const TeamItem = React.memo(({ userId, team, isTeamPage }) => {
+const TeamItem = React.memo(({ team, isTeamPage }) => {
const { data: session } = useSession();
-
const router = useRouter();
- const isSAdmin = session?.user.isSAdmin;
-
+ const { id: userId, isSAdmin } = { ...session?.user };
const { id, users, name } = team;
const userFound: TeamUser | undefined = users.find((member) => member.user?._id === userId);
const userRole = userFound?.role;
@@ -61,7 +58,7 @@ const TeamItem = React.memo(({ userId, team, isTeamPage }) => {
}, [isSAdmin, team, userId]);
return (
-
+
{
const renderTitle = () => (
-
+
{title}
);
diff --git a/frontend/src/components/Teams/TeamsList/TeamsList.spec.tsx b/frontend/src/components/Teams/TeamsList/TeamsList.spec.tsx
new file mode 100644
index 000000000..4c367e01c
--- /dev/null
+++ b/frontend/src/components/Teams/TeamsList/TeamsList.spec.tsx
@@ -0,0 +1,52 @@
+import { createMockRouter } from '@/utils/testing/mocks';
+import { renderWithProviders } from '@/utils/testing/renderWithProviders';
+import { TeamFactory } from '@/utils/factories/team';
+import { fireEvent, waitFor } from '@testing-library/react';
+import { ROUTES } from '@/utils/routes';
+import TeamsList, { TeamsListProps } from '.';
+
+const DEFAULT_PROPS = {
+ teams: TeamFactory.createMany(3),
+};
+
+const router = createMockRouter({ pathname: '/teams' });
+
+jest.mock('next/router', () => ({
+ useRouter: () => router,
+}));
+
+const render = (props: TeamsListProps = DEFAULT_PROPS) =>
+ renderWithProviders(, { routerOptions: router });
+
+describe('Components/TeamsList', () => {
+ let testProps: TeamsListProps;
+ beforeEach(() => {
+ testProps = { ...DEFAULT_PROPS };
+ });
+
+ it('should render correctly', () => {
+ // Arrange
+ const teamItemProps = { ...testProps };
+
+ // Act
+ const { getAllByTestId } = render(teamItemProps);
+
+ // Assert
+ expect(getAllByTestId('teamItem')).toHaveLength(teamItemProps.teams.length);
+ });
+
+ it('should render empty state correctly', async () => {
+ // Arrange
+ const teamItemProps = { ...testProps, teams: [] };
+
+ // Act
+ const { getByTestId, getByText } = render(teamItemProps);
+ fireEvent.click(getByText('Create your first team'));
+
+ // Assert
+ expect(getByTestId('emptyTeams')).toBeInTheDocument();
+ await waitFor(() => {
+ expect(router.push).toHaveBeenCalledWith(ROUTES.NewTeam, ROUTES.NewTeam, expect.anything());
+ });
+ });
+});
diff --git a/frontend/src/components/Teams/TeamsList/index.tsx b/frontend/src/components/Teams/TeamsList/index.tsx
index 7f6c3d100..0cf7c2eef 100644
--- a/frontend/src/components/Teams/TeamsList/index.tsx
+++ b/frontend/src/components/Teams/TeamsList/index.tsx
@@ -1,19 +1,25 @@
import React from 'react';
import { Team } from '@/types/team/team';
+import Flex from '@/components/Primitives/Flex';
import EmptyTeams from './partials/EmptyTeams';
-import ListOfCards from './partials/ListOfCards';
-type TeamsListProps = {
- userId: string;
+import TeamItem from './TeamItem';
+
+export type TeamsListProps = {
teams: Team[];
- isFetching: boolean;
};
-const TeamsList = ({ userId, teams, isFetching }: TeamsListProps) => {
+const TeamsList = ({ teams }: TeamsListProps) => {
if (teams?.length === 0) return ;
- return ;
+ return (
+
+ {teams.map((team: Team) => (
+
+ ))}
+
+ );
};
export default TeamsList;
diff --git a/frontend/src/components/Teams/TeamsList/partials/EmptyTeams.tsx b/frontend/src/components/Teams/TeamsList/partials/EmptyTeams.tsx
new file mode 100644
index 000000000..811fd0eea
--- /dev/null
+++ b/frontend/src/components/Teams/TeamsList/partials/EmptyTeams.tsx
@@ -0,0 +1,37 @@
+import { styled } from '@/styles/stitches/stitches.config';
+import Link from 'next/link';
+
+import Text from '@/components/Primitives/Text';
+import Box from '@/components/Primitives/Box';
+import Flex from '@/components/Primitives/Flex';
+
+import EmptyTeamsImage from '@/components/images/EmptyTeams';
+
+const StyledBox = styled(Flex, Box, {
+ position: 'relative',
+ borderRadius: '$12',
+ backgroundColor: 'white',
+ mt: '$14',
+ p: '$48',
+});
+
+const EmptyTeams = () => (
+
+
+
+
+
+ Create your first team
+
+ {' '}
+ now.
+
+
+);
+export default EmptyTeams;
diff --git a/frontend/src/components/Teams/TeamsList/partials/EmptyTeams/index.tsx b/frontend/src/components/Teams/TeamsList/partials/EmptyTeams/index.tsx
deleted file mode 100644
index eacffa019..000000000
--- a/frontend/src/components/Teams/TeamsList/partials/EmptyTeams/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import Link from 'next/link';
-
-import { EmptyBoardsText, StyledBox, StyledImage, StyledNewTeamLink } from './styles';
-
-const EmptyTeams: React.FC = () => (
-
-
-
-
-
-
- Create your first team
-
- {' '}
- now.
-
-
-);
-export default EmptyTeams;
diff --git a/frontend/src/components/Teams/TeamsList/partials/EmptyTeams/styles.tsx b/frontend/src/components/Teams/TeamsList/partials/EmptyTeams/styles.tsx
deleted file mode 100644
index 0f6eba95d..000000000
--- a/frontend/src/components/Teams/TeamsList/partials/EmptyTeams/styles.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { styled } from '@/styles/stitches/stitches.config';
-
-import EmptyTeamsImage from '@/components/images/EmptyTeams';
-import Box from '@/components/Primitives/Box';
-import Flex from '@/components/Primitives/Flex';
-import Text from '@/components/Primitives/Text';
-
-const StyledImage = styled(EmptyTeamsImage, Flex, Box, { '& svg': { zIndex: '-1' } });
-
-const StyledBox = styled(Flex, Box, {
- position: 'relative',
- width: '100%',
- mt: '$14',
- backgroundColor: 'white',
- pt: '$48',
- borderRadius: '$12',
-});
-
-const EmptyBoardsText = styled(Text, {
- mb: '48px',
-});
-
-const StyledNewTeamLink = styled('a', Text, { cursor: 'pointer' });
-
-export { EmptyBoardsText, StyledBox, StyledImage, StyledNewTeamLink };
diff --git a/frontend/src/components/Teams/TeamsList/partials/ListOfCards/index.tsx b/frontend/src/components/Teams/TeamsList/partials/ListOfCards/index.tsx
deleted file mode 100644
index 9eaf9c4bf..000000000
--- a/frontend/src/components/Teams/TeamsList/partials/ListOfCards/index.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-
-import Dots from '@/components/Primitives/Loading/Dots';
-import Flex from '@/components/Primitives/Flex';
-import { Team } from '@/types/team/team';
-
-import { ScrollableContent } from '@/components/Boards/MyBoards/styles';
-import TeamItem from '@/components/Teams/TeamsList/TeamItem';
-
-type ListOfCardsProp = {
- teams: Team[];
- userId: string;
- isLoading: boolean;
-};
-
-const ListOfCards = React.memo(({ teams, userId, isLoading }) => (
-
-
- {teams.map((team: Team) => (
-
- ))}
-
-
- {isLoading && (
-
-
-
- )}
-
-));
-
-export default ListOfCards;
diff --git a/frontend/src/components/Teams/TeamsList/partials/ListOfCards/styles.tsx b/frontend/src/components/Teams/TeamsList/partials/ListOfCards/styles.tsx
deleted file mode 100644
index 7cae40ecd..000000000
--- a/frontend/src/components/Teams/TeamsList/partials/ListOfCards/styles.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { styled } from '@/styles/stitches/stitches.config';
-
-import Text from '@/components/Primitives/Text';
-
-const LastUpdatedText = styled(Text, {
- position: 'sticky',
- zIndex: '5',
- top: '-0.2px',
- height: '$24',
- backgroundColor: '$background',
-});
-
-export { LastUpdatedText };
diff --git a/frontend/src/components/Users/UserEdit/index.tsx b/frontend/src/components/Users/UserEdit/index.tsx
index b4d1fb251..40a695ec8 100644
--- a/frontend/src/components/Users/UserEdit/index.tsx
+++ b/frontend/src/components/Users/UserEdit/index.tsx
@@ -3,12 +3,9 @@ import React from 'react';
import ListOfCards from './partials/ListOfCards';
type UserEditProps = {
- userId: string | undefined;
isLoading: boolean;
};
-const UsersEdit = ({ userId, isLoading }: UserEditProps) => (
-
-);
+const UsersEdit = ({ isLoading }: UserEditProps) => ;
export default UsersEdit;
diff --git a/frontend/src/components/Users/UserEdit/partials/ListOfCards/index.tsx b/frontend/src/components/Users/UserEdit/partials/ListOfCards/index.tsx
index 06d5dd2d1..57e697873 100644
--- a/frontend/src/components/Users/UserEdit/partials/ListOfCards/index.tsx
+++ b/frontend/src/components/Users/UserEdit/partials/ListOfCards/index.tsx
@@ -9,18 +9,17 @@ import { useRecoilValue } from 'recoil';
import { userTeamsListState } from '@/store/team/atom/team.atom';
type ListOfCardsProp = {
- userId: string | undefined;
isLoading: boolean;
};
-const ListOfCards = React.memo(({ userId, isLoading }) => {
+const ListOfCards = React.memo(({ isLoading }) => {
const teamsOfUsers = useRecoilValue(userTeamsListState);
return (
{teamsOfUsers?.map((team: Team) => (
-
+
))}
{isLoading && (
diff --git a/frontend/src/components/layouts/Layout/index.tsx b/frontend/src/components/layouts/Layout/index.tsx
index d8069049f..358b2c9d6 100644
--- a/frontend/src/components/layouts/Layout/index.tsx
+++ b/frontend/src/components/layouts/Layout/index.tsx
@@ -5,7 +5,7 @@ import { signOut, useSession } from 'next-auth/react';
import Flex from '@/components/Primitives/Flex';
import LoadingPage from '@/components/Primitives/Loading/Page';
import Sidebar from '@/components/Sidebar';
-import { BOARDS_ROUTE, DASHBOARD_ROUTE, TEAMS_ROUTE, USERS_ROUTE } from '@/utils/routes';
+import { ROUTES } from '@/utils/routes';
import { REFRESH_TOKEN_ERROR } from '@/utils/constants';
import MainPageHeader from './partials/MainPageHeader';
import { Container, ContentSection } from './styles';
@@ -23,37 +23,37 @@ const Layout: React.FC<{ children: ReactNode }> = ({ children }) => {
if (!session) return null;
switch (router.pathname) {
- case DASHBOARD_ROUTE:
+ case ROUTES.Dashboard:
return (
);
- case BOARDS_ROUTE:
+ case ROUTES.Boards:
return (
);
- case TEAMS_ROUTE:
+ case ROUTES.Teams:
return (
);
- case USERS_ROUTE:
+ case ROUTES.Users:
return ;
default:
return null;
diff --git a/frontend/src/pages/teams/index.tsx b/frontend/src/pages/teams/index.tsx
index 517f72433..e0c71d049 100644
--- a/frontend/src/pages/teams/index.tsx
+++ b/frontend/src/pages/teams/index.tsx
@@ -14,6 +14,7 @@ import requireAuthentication from '@/components/HOC/requireAuthentication';
import { useRecoilState } from 'recoil';
import { teamsListState } from '@/store/team/atom/team.atom';
import TeamsList from '@/components/Teams/TeamsList';
+import Dots from '@/components/Primitives/Loading/Dots';
const Teams = () => {
const { data: session } = useSession({ required: true });
@@ -30,10 +31,24 @@ const Teams = () => {
if (!session || !data) return null;
return (
-
+
}>
-
+ {isFetching ? (
+
+
+
+ ) : (
+
+ )}
diff --git a/frontend/src/pages/users/[userId].tsx b/frontend/src/pages/users/[userId].tsx
index 8f8e53585..848552feb 100644
--- a/frontend/src/pages/users/[userId].tsx
+++ b/frontend/src/pages/users/[userId].tsx
@@ -59,7 +59,7 @@ const UserDetails = () => {
joinedAt={user.joinedAt}
/>
- {data && }
+ {data && }
diff --git a/frontend/src/stories/Teams/TeamCard.stories.tsx b/frontend/src/stories/Teams/TeamCard.stories.tsx
index 9c4653497..9209c1846 100644
--- a/frontend/src/stories/Teams/TeamCard.stories.tsx
+++ b/frontend/src/stories/Teams/TeamCard.stories.tsx
@@ -30,7 +30,7 @@ const Template: ComponentStory = ({ team, isTeamPage }) => {
createTeamUser(user, team);
}
- return ;
+ return ;
};
export const Default = Template.bind({});
diff --git a/frontend/src/utils/routes.ts b/frontend/src/utils/routes.ts
index d962fdbcf..16aa2c15e 100644
--- a/frontend/src/utils/routes.ts
+++ b/frontend/src/utils/routes.ts
@@ -8,19 +8,22 @@ export const ACCOUNT_ROUTE = '/account';
export const SETTINGS_ROUTE = '/settings';
export const ERROR_500_PAGE = '/500';
export const AZURE_LOGOUT_ROUTE = '/logoutAzure';
+export const LOGIN_GUEST_USER = '/login-guest-user';
export const ROUTES = {
START_PAGE_ROUTE,
Dashboard: DASHBOARD_ROUTE,
Boards: BOARDS_ROUTE,
- BoardPage: (boardId: string): string => `/boards/${boardId}`,
+ BoardPage: (boardId: string): string => `${BOARDS_ROUTE}/${boardId}`,
+ NewBoard: `${BOARDS_ROUTE}/new`,
Token: RESET_PASSWORD_ROUTE,
- TokenPage: (tokenId: string): string => `/reset-password/${tokenId}`,
+ TokenPage: (tokenId: string): string => `${RESET_PASSWORD_ROUTE}/${tokenId}`,
Teams: TEAMS_ROUTE,
- TeamPage: (teamId: string): string => `/teams/${teamId}`,
+ TeamPage: (teamId: string): string => `${TEAMS_ROUTE}/${teamId}`,
+ NewTeam: `${TEAMS_ROUTE}/new`,
Users: USERS_ROUTE,
- UserEdit: (userId: string) => `/users/${userId}`,
- UserGuest: (boardId: string) => `/login-guest-user/${boardId}`,
+ UserEdit: (userId: string) => `${USERS_ROUTE}/${userId}`,
+ UserGuest: (boardId: string) => `${LOGIN_GUEST_USER}/${boardId}`,
};
export const GetPageTitleByUrl = (url: string): string | undefined =>
diff --git a/frontend/src/utils/testing/mocks.ts b/frontend/src/utils/testing/mocks.ts
index ad3bd8463..79aaf99d3 100644
--- a/frontend/src/utils/testing/mocks.ts
+++ b/frontend/src/utils/testing/mocks.ts
@@ -1,3 +1,4 @@
+import { User } from '@/types/user/user';
import { Session } from 'next-auth/core/types';
import { NextRouter } from 'next/router';
import { SessionUserFactory } from '../factories/user';
@@ -32,9 +33,9 @@ export function createMockRouter(router?: Partial): NextRouter {
};
}
-export function createMockSession(session?: Partial): Session {
+export function createMockSession(session?: Partial, user?: User): Session {
return {
- user: SessionUserFactory.create({ isSAdmin: false }),
+ user: SessionUserFactory.create({ ...user, id: user?._id, isSAdmin: false }),
expires: new Date().toISOString(),
strategy: 'local',
error: '',
diff --git a/frontend/src/utils/testing/renderWithProviders.tsx b/frontend/src/utils/testing/renderWithProviders.tsx
index 07c8c8c79..c4e91a688 100644
--- a/frontend/src/utils/testing/renderWithProviders.tsx
+++ b/frontend/src/utils/testing/renderWithProviders.tsx
@@ -1,3 +1,4 @@
+import { User } from '@/types/user/user';
import { createMockRouter, createMockSession } from '@/utils/testing/mocks';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render as rtlRender, RenderOptions, RenderResult } from '@testing-library/react';
@@ -11,7 +12,10 @@ import { RecoilRoot } from 'recoil';
export type RenderWithProvidersOptions = Omit & {
routerOptions?: Partial;
queryClient?: QueryClient;
- sessionOptions?: Session;
+ sessionOptions?: {
+ session?: Session;
+ user?: User;
+ };
};
export function renderWithProviders(
@@ -22,7 +26,10 @@ export function renderWithProviders(
wrapper: ({ children }: { children: ReactNode }) => {
const router = createMockRouter(options?.routerOptions);
const queryClient = options?.queryClient ?? new QueryClient();
- const session = createMockSession(options?.sessionOptions);
+ const session = createMockSession(
+ options?.sessionOptions?.session,
+ options?.sessionOptions?.user,
+ );
return (