diff --git a/.all-contributorsrc b/.all-contributorsrc
index b029b66b5..ccc289fc2 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -119,6 +119,40 @@
"code",
"doc"
]
+ },
+ {
+ "login": "CatiaAntunes96",
+ "name": "CΓ‘tia Antunes",
+ "avatar_url": "https://avatars.githubusercontent.com/u/59372326?v=4",
+ "profile": "https://github.com/CatiaAntunes96",
+ "contributions": [
+ "code",
+ "doc",
+ "review"
+ ]
+ },
+ {
+ "login": "mourabraz",
+ "name": "mourabraz",
+ "avatar_url": "https://avatars.githubusercontent.com/u/7543719?v=4",
+ "profile": "https://www.linkedin.com/in/moura-braz",
+ "contributions": [
+ "code",
+ "doc",
+ "review"
+ ]
+ },
+ {
+ "login": "miguel-felix1",
+ "name": "Miguel FΓ©lix",
+ "avatar_url": "https://avatars.githubusercontent.com/u/87712174?v=4",
+ "profile": "https://github.com/miguel-felix1",
+ "contributions": [
+ "code",
+ "bug",
+ "review",
+ "userTesting"
+ ]
}
],
"contributorsPerLine": 7,
@@ -126,5 +160,6 @@
"projectOwner": "xgeekshq",
"repoType": "github",
"repoHost": "https://github.com",
- "skipCi": false
+ "skipCi": false,
+ "commitConvention": "angular"
}
diff --git a/README.md b/README.md
index a8a891925..6dc42b913 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
- [ππ» How to Contribute](#--how-to-contribute)
- [π How to Run](https://github.com/xgeekshq/split/wiki/How-to-run)
- [π Requirements](https://github.com/xgeekshq/split/wiki/Requirements)
-- [Usage](#usage)
+- [:computer: Usage](#usage)
- [π License](#-license)
- [Contributors β¨](#contributors-)
@@ -35,10 +35,9 @@ Check out our [**Contributing Guide**](.github/CONTRIBUTING.md) for information
The backend will run on `http://localhost:BACKEND_PORT` and the frontend on `http://localhost:3000`.
- `/dashboard`: dashboard
+- `/boards`: boards list
- `/boards/[boardId]`: board page
-You must register to access the dashboard page.
-
## π License
Licensed under the [MIT License](./LICENSE).
@@ -51,21 +50,26 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
diff --git a/backend/package-lock.json b/backend/package-lock.json
index aea10b758..a0831e4d0 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -13901,9 +13901,9 @@
}
},
"node_modules/vm2": {
- "version": "3.9.10",
- "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.10.tgz",
- "integrity": "sha512-AuECTSvwu2OHLAZYhG716YzwodKCIJxB6u1zG7PgSQwIgAlEaoXH52bxdcvT8GkGjnYK7r7yWDW0m0sOsPuBjQ==",
+ "version": "3.9.11",
+ "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz",
+ "integrity": "sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==",
"dependencies": {
"acorn": "^8.7.0",
"acorn-walk": "^8.2.0"
@@ -24929,9 +24929,9 @@
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
},
"vm2": {
- "version": "3.9.10",
- "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.10.tgz",
- "integrity": "sha512-AuECTSvwu2OHLAZYhG716YzwodKCIJxB6u1zG7PgSQwIgAlEaoXH52bxdcvT8GkGjnYK7r7yWDW0m0sOsPuBjQ==",
+ "version": "3.9.11",
+ "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz",
+ "integrity": "sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==",
"requires": {
"acorn": "^8.7.0",
"acorn-walk": "^8.2.0"
diff --git a/backend/src/libs/dto/param/team.param.optional.ts b/backend/src/libs/dto/param/team.param.optional.ts
new file mode 100644
index 000000000..7b8ce2e3f
--- /dev/null
+++ b/backend/src/libs/dto/param/team.param.optional.ts
@@ -0,0 +1,6 @@
+import { IsOptional } from 'class-validator';
+
+export class TeamParamOptional {
+ @IsOptional()
+ teamId?: string;
+}
diff --git a/backend/src/modules/boards/controller/boards.controller.ts b/backend/src/modules/boards/controller/boards.controller.ts
index fd2eaf021..7108fd871 100644
--- a/backend/src/modules/boards/controller/boards.controller.ts
+++ b/backend/src/modules/boards/controller/boards.controller.ts
@@ -49,6 +49,7 @@ import { UnauthorizedResponse } from 'libs/swagger/errors/unauthorized.swagger';
import { BoardResponse } from 'modules/boards/swagger/board.swagger';
import SocketGateway from 'modules/socket/gateway/socket.gateway';
+import { TeamParamOptional } from '../../../libs/dto/param/team.param.optional';
import BoardDto from '../dto/board.dto';
import { UpdateBoardDto } from '../dto/update-board.dto';
import { CreateBoardApplicationInterface } from '../interfaces/applications/create.board.application.interface';
@@ -238,10 +239,20 @@ export default class BoardsController {
type: InternalServerErrorResponse
})
@Delete(':boardId')
- async deleteBoard(@Param() { boardId }: BaseParam, @Req() request: RequestWithUser) {
+ async deleteBoard(
+ @Param() { boardId }: BaseParam,
+ @Query() { teamId }: TeamParamOptional,
+ @Query() { socketId }: BaseParamWSocket,
+ @Req() request: RequestWithUser
+ ) {
const result = await this.deleteBoardApp.delete(boardId, request.user._id);
if (!result) throw new BadRequestException(DELETE_FAILED);
+ if (socketId && teamId) {
+ this.socketService.sendUpdatedBoards(socketId, teamId);
+ this.socketService.sendUpdatedBoard(boardId, socketId);
+ }
+
return result;
}
diff --git a/backend/src/modules/queue/queue.module.ts b/backend/src/modules/queue/queue.module.ts
index fe1d53b72..8fc9b8308 100644
--- a/backend/src/modules/queue/queue.module.ts
+++ b/backend/src/modules/queue/queue.module.ts
@@ -8,7 +8,6 @@ import { ConfigService } from '@nestjs/config';
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
redis: {
- username: configService.get('redis.user'),
password: configService.get('redis.password'),
host: configService.get('redis.host'),
port: configService.get('redis.port'),
diff --git a/backend/src/modules/schedules/services/create.schedules.service.ts b/backend/src/modules/schedules/services/create.schedules.service.ts
index 61aa24d8b..f8ec0ffb0 100644
--- a/backend/src/modules/schedules/services/create.schedules.service.ts
+++ b/backend/src/modules/schedules/services/create.schedules.service.ts
@@ -44,7 +44,6 @@ export class CreateSchedulesService implements CreateSchedulesServiceInterface {
job.start();
} catch (e) {
await this.schedulesModel.deleteOne({ board: boardId });
- this.schedulerRegistry.deleteCronJob(boardId);
}
}
diff --git a/backend/src/modules/socket/gateway/socket.gateway.ts b/backend/src/modules/socket/gateway/socket.gateway.ts
index a01c0edfc..30ed7f07c 100644
--- a/backend/src/modules/socket/gateway/socket.gateway.ts
+++ b/backend/src/modules/socket/gateway/socket.gateway.ts
@@ -9,6 +9,7 @@ import {
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
+import JoinPayload from '../interfaces/joinPayload.interface';
import JoinPayloadBoards from '../interfaces/joinPayloadBoards.interface';
@WebSocketGateway({ cors: true })
@@ -24,8 +25,8 @@ export default class SocketGateway
this.server.to(newBoardId.toString()).except(excludedClient).emit('updateAllBoard', newBoardId);
}
- sendUpdatedBoards(excludedClient: string) {
- this.server.except(excludedClient).emit('updateBoardList');
+ sendUpdatedBoards(excludedClient: string, teamId: string) {
+ this.server.to(teamId).except(excludedClient).emit('teamId');
}
sendUpdatedAllBoard(excludedClient: string) {
@@ -44,8 +45,13 @@ export default class SocketGateway
this.logger.log(`Client connected: ${client.id}`);
}
+ @SubscribeMessage('join')
+ handleJoin(client: Socket, payload: JoinPayload) {
+ client.join(payload.boardId);
+ }
+
@SubscribeMessage('joinBoards')
handleJoinBoards(client: Socket, payload: JoinPayloadBoards) {
- client.join(payload.boards);
+ client.join(payload.teamId);
}
}
diff --git a/backend/src/modules/socket/interfaces/joinPayloadBoards.interface.ts b/backend/src/modules/socket/interfaces/joinPayloadBoards.interface.ts
index 9364daad7..3a8f317d0 100644
--- a/backend/src/modules/socket/interfaces/joinPayloadBoards.interface.ts
+++ b/backend/src/modules/socket/interfaces/joinPayloadBoards.interface.ts
@@ -1,3 +1,3 @@
export default interface JoinPayloadBoards {
- boards: string;
+ teamId: string;
}
diff --git a/frontend/src/api/boardService.tsx b/frontend/src/api/boardService.tsx
index 0ede42184..360fac0b9 100644
--- a/frontend/src/api/boardService.tsx
+++ b/frontend/src/api/boardService.tsx
@@ -49,8 +49,16 @@ export const getBoardsRequest = (
return fetchData(`/boards?page=${pageParam ?? 0}&size=10`, { context, serverSide: !!context });
};
-export const deleteBoardRequest = async (id: string): Promise => {
- return fetchData(`/boards/${id}`, { method: 'DELETE' });
+export const deleteBoardRequest = async ({
+ id,
+ socketId,
+ teamId
+}: {
+ id: string;
+ socketId?: string;
+ teamId: string;
+}): Promise => {
+ return fetchData(`/boards/${id}`, { method: 'DELETE', params: { socketId, teamId } });
};
// #endregion
diff --git a/frontend/src/components/Board/Header/index.tsx b/frontend/src/components/Board/Header/index.tsx
index e2e918510..0fe71e5f3 100644
--- a/frontend/src/components/Board/Header/index.tsx
+++ b/frontend/src/components/Board/Header/index.tsx
@@ -149,6 +149,7 @@ const BoardHeader = () => {
{isSubBoard ? title.replace('board', '') : team.name}
({
+ const initialData: UpdateBoardType = {
_id: board?._id,
hideCards: board?.hideCards,
hideVotes: board?.hideVotes,
title: board?.title,
maxVotes: board?.maxVotes,
users: board?.users
- });
+ };
+ const [data, setData] = useState(initialData);
// References
const dialogContainerRef = useRef(null);
@@ -87,10 +88,8 @@ const BoardSettings = ({
updateBoard: { mutate }
} = useBoard({ autoFetchBoard: false });
- const responsible = useMemo(
- () => data.users?.find((user) => user.role === BoardUserRoles.RESPONSIBLE)?.user,
- [data]
- );
+ const responsible = data.users?.find((user) => user.role === BoardUserRoles.RESPONSIBLE)?.user;
+
// Use Form Hook
const methods = useForm<{ title: string; maxVotes?: number | null }>({
mode: 'onBlur',
@@ -105,6 +104,7 @@ const BoardSettings = ({
// Method to close dialog and reset switches state
const handleClose = () => {
setIsOpen(false);
+ setData(initialData);
setSwitchesState({
maxVotes: false,
responsible: false
diff --git a/frontend/src/components/Boards/MyBoards/index.tsx b/frontend/src/components/Boards/MyBoards/index.tsx
index 7aa787b91..2220434df 100644
--- a/frontend/src/components/Boards/MyBoards/index.tsx
+++ b/frontend/src/components/Boards/MyBoards/index.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo, useRef } from 'react';
+import React, { useEffect, useMemo, useRef } from 'react';
import { useInfiniteQuery } from 'react-query';
import { useSetRecoilState } from 'recoil';
@@ -7,6 +7,7 @@ import CardBody from 'components/CardBoard/CardBody/CardBody';
import LoadingPage from 'components/loadings/LoadingPage';
import Flex from 'components/Primitives/Flex';
import Text from 'components/Primitives/Text';
+import { useSocketBoardIO } from 'hooks/useSocketBoardIO';
import { toastState } from 'store/toast/atom/toast.atom';
import BoardType from 'types/board/board';
import { Team } from 'types/team/team';
@@ -46,6 +47,19 @@ const MyBoards = React.memo(({ userId, isSuperAdmin }) => {
);
const { data, isLoading } = fetchBoards;
+
+ const teamSocketId = data?.pages[0].boards[0] ? data?.pages[0].boards[0].team._id : undefined;
+
+ // socketId
+ const { socket, queryClient } = useSocketBoardIO(teamSocketId);
+
+ useEffect(() => {
+ if (!socket) return;
+ socket.on('teamId', () => {
+ queryClient.invalidateQueries('boards');
+ });
+ }, [socket, queryClient]);
+
const currentDate = new Date().toDateString();
const dataByTeamAndDate = useMemo(() => {
@@ -84,10 +98,6 @@ const MyBoards = React.memo(({ userId, isSuperAdmin }) => {
}
};
- // const teamNames = Array.from(dataByTeamAndDate.teams.values()).map((team) => {
- // return { value: team._id, label: team.name };
- // });
-
return (
{/* */}
@@ -176,6 +186,7 @@ const MyBoards = React.memo(({ userId, isSuperAdmin }) => {
dividedBoardsCount={board.dividedBoards.length}
isDashboard={false}
isSAdmin={isSuperAdmin}
+ socketId={socket?.id}
userId={userId}
/>
))}
diff --git a/frontend/src/components/Boards/MyBoards/styles.tsx b/frontend/src/components/Boards/MyBoards/styles.tsx
index e760ad167..5cc736383 100644
--- a/frontend/src/components/Boards/MyBoards/styles.tsx
+++ b/frontend/src/components/Boards/MyBoards/styles.tsx
@@ -4,7 +4,7 @@ import Flex from 'components/Primitives/Flex';
const ScrollableContent = styled(Flex, {
mt: '$24',
- height: 'calc(100vh - 300px)',
+ height: 'calc(100vh - 180px)',
overflowY: 'auto',
pr: '$10',
pb: '$10'
diff --git a/frontend/src/components/CardBoard/CardAvatars.tsx b/frontend/src/components/CardBoard/CardAvatars.tsx
index 2afef5ba4..27933255e 100644
--- a/frontend/src/components/CardBoard/CardAvatars.tsx
+++ b/frontend/src/components/CardBoard/CardAvatars.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
import Avatar from 'components/Primitives/Avatar';
import Flex from 'components/Primitives/Flex';
@@ -6,6 +6,7 @@ import Tooltip from 'components/Primitives/Tooltip';
import { User } from 'types/user/user';
import { BoardUserRoles } from 'utils/enums/board.user.roles';
import { TeamUserRoles } from 'utils/enums/team.user.roles';
+import { IconButton } from './styles';
type ListUsersType = {
user: User | string;
@@ -21,10 +22,26 @@ type CardAvatarProps = {
userId: string;
myBoards?: boolean;
haveError?: boolean;
+ isBoardsPage?: boolean;
};
const CardAvatars = React.memo(
- ({ listUsers, teamAdmins, stakeholders, userId, haveError, responsible, myBoards }) => {
+ ({
+ listUsers,
+ teamAdmins,
+ stakeholders,
+ userId,
+ haveError,
+ responsible,
+ myBoards,
+ isBoardsPage
+ }) => {
+ const [viewAllUsers, setViewAllUsers] = useState(false);
+
+ const handleViewAllUsers = useCallback(() => {
+ setViewAllUsers(!viewAllUsers);
+ }, [viewAllUsers]);
+
const data = useMemo(() => {
if (responsible)
return listUsers
@@ -43,7 +60,7 @@ const CardAvatars = React.memo(
}
return listUsers.reduce((acc: User[], userFound: ListUsersType) => {
- if ((userFound.user as User)._id === userId) {
+ if ((userFound.user as User)?._id === userId) {
acc.unshift(userFound.user as User);
} else {
acc.push(userFound.user as User);
@@ -56,12 +73,12 @@ const CardAvatars = React.memo(
const getInitials = useCallback(
(user: User | undefined, index) => {
- if (usersCount - 1 > index && index > 1) {
+ if (!viewAllUsers && usersCount - 1 > index && index > 1) {
return `+${usersCount - 2}`;
}
return user ?? '--';
},
- [usersCount]
+ [usersCount, viewAllUsers]
);
const stakeholdersColors = useMemo(
@@ -77,15 +94,30 @@ const CardAvatars = React.memo(
(value: User | string, avatarColor, idx) => {
if (typeof value === 'string') {
return (
- 0 ? '-7px' : 0 }}
- fallbackText={value}
- id={value}
- isDefaultColor={value === userId}
- size={32}
- />
+ aria-hidden="true"
+ disabled={!isBoardsPage}
+ type="button"
+ css={{
+ '&:hover': isBoardsPage
+ ? {
+ cursor: 'pointer'
+ }
+ : 'none'
+ }}
+ onClick={handleViewAllUsers}
+ >
+ 0 ? '-7px' : 0 }}
+ fallbackText={value}
+ id={value}
+ isDefaultColor={value === userId}
+ size={32}
+ />
+
);
}
@@ -95,7 +127,19 @@ const CardAvatars = React.memo(
key={`${value}-${idx}-${Math.random()}`}
content={`${value.firstName} ${value.lastName}`}
>
-
+
(
isDefaultColor={value._id === userId}
size={32}
/>
-
+
);
},
- [userId]
+ [handleViewAllUsers, userId, isBoardsPage]
);
+ const numberOfAvatars = useMemo(() => {
+ if (!myBoards) {
+ return viewAllUsers && isBoardsPage ? data.length : 3;
+ }
+
+ return 1;
+ }, [data.length, isBoardsPage, myBoards, viewAllUsers]);
+
return (
{haveError
@@ -122,7 +174,7 @@ const CardAvatars = React.memo(
index
);
})
- : (data.slice(0, !myBoards ? 3 : 1) as User[]).map(
+ : (data.slice(0, numberOfAvatars) as User[]).map(
(user: User, index: number) => {
return renderAvatar(
getInitials(user, index),
diff --git a/frontend/src/components/CardBoard/CardBody/CardBody.tsx b/frontend/src/components/CardBoard/CardBody/CardBody.tsx
index d6f74b1fa..67287e64d 100644
--- a/frontend/src/components/CardBoard/CardBody/CardBody.tsx
+++ b/frontend/src/components/CardBoard/CardBody/CardBody.tsx
@@ -90,20 +90,30 @@ type CardBodyProps = {
isDashboard: boolean;
mainBoardId?: string;
isSAdmin?: boolean;
+ socketId?: string;
};
const CardBody = React.memo(
- ({ userId, board, index, isDashboard, dividedBoardsCount, mainBoardId, isSAdmin }) => {
+ ({
+ userId,
+ board,
+ index,
+ isDashboard,
+ dividedBoardsCount,
+ mainBoardId,
+ isSAdmin,
+ socketId
+ }) => {
const { _id: id, columns, users, team, dividedBoards, isSubBoard } = board;
const countDividedBoards = dividedBoardsCount || dividedBoards.length;
- const [openSubBoards, setSubBoardsOpen] = useState(true);
+ const [openSubBoards, setSubBoardsOpen] = useState(false);
const newBoard = useRecoilValue(newBoardState);
const isANewBoard = newBoard?._id === board._id;
const userIsParticipating = useMemo(() => {
- return !!users.find((user) => user.user._id === userId);
+ return !!users.find((user) => user.user?._id === userId);
}, [users, userId]);
const havePermissions = useMemo(() => {
@@ -136,11 +146,12 @@ const CardBody = React.memo(
index={idx}
isDashboard={isDashboard}
mainBoardId={board._id}
+ socketId={socketId}
userId={userId}
/>
);
},
- [board._id, countDividedBoards, isDashboard, userId]
+ [board._id, countDividedBoards, isDashboard, userId, socketId]
);
const iconLockConditions =
@@ -237,6 +248,7 @@ const CardBody = React.memo(
index={index}
isDashboard={isDashboard}
isSubBoard={isSubBoard}
+ socketId={socketId}
userId={userId}
userIsParticipating={userIsParticipating}
userSAdmin={isSAdmin}
diff --git a/frontend/src/components/CardBoard/CardBody/CardEnd.tsx b/frontend/src/components/CardBoard/CardBody/CardEnd.tsx
index f22a827ff..bcb4581bb 100644
--- a/frontend/src/components/CardBoard/CardBody/CardEnd.tsx
+++ b/frontend/src/components/CardBoard/CardBody/CardEnd.tsx
@@ -17,6 +17,7 @@ type CardEndProps = {
userId: string;
userSAdmin?: boolean;
userIsParticipating: boolean;
+ socketId?: string;
};
const CardEnd: React.FC = React.memo(
@@ -27,7 +28,8 @@ const CardEnd: React.FC = React.memo(
index,
havePermissions,
userId,
- userSAdmin = undefined
+ userSAdmin = undefined,
+ socketId
}) => {
CardEnd.defaultProps = {
userSAdmin: undefined
@@ -102,7 +104,12 @@ const CardEnd: React.FC = React.memo(
}}
/>
-
+
)}
diff --git a/frontend/src/components/CardBoard/DeleteBoard.tsx b/frontend/src/components/CardBoard/DeleteBoard.tsx
index 67b7511d2..a54f3af60 100644
--- a/frontend/src/components/CardBoard/DeleteBoard.tsx
+++ b/frontend/src/components/CardBoard/DeleteBoard.tsx
@@ -5,13 +5,18 @@ import Flex from 'components/Primitives/Flex';
import Tooltip from 'components/Primitives/Tooltip';
import useBoard from 'hooks/useBoard';
-type DeleteBoardProps = { boardId: string; boardName: string };
+type DeleteBoardProps = {
+ boardId: string;
+ boardName: string;
+ socketId?: string;
+ teamId: string;
+};
-const DeleteBoard: React.FC = ({ boardId, boardName }) => {
+const DeleteBoard: React.FC = ({ boardId, boardName, socketId, teamId }) => {
const { deleteBoard } = useBoard({ autoFetchBoard: false });
const handleDelete = () => {
- deleteBoard.mutate(boardId);
+ deleteBoard.mutate({ id: boardId, socketId, teamId });
};
return (
diff --git a/frontend/src/components/CardBoard/styles.tsx b/frontend/src/components/CardBoard/styles.tsx
new file mode 100644
index 000000000..8288a7dd3
--- /dev/null
+++ b/frontend/src/components/CardBoard/styles.tsx
@@ -0,0 +1,8 @@
+import { styled } from 'styles/stitches/stitches.config';
+
+const IconButton = styled('button', {
+ border: 'none',
+ backgroundColor: 'transparent'
+});
+
+export { IconButton };
diff --git a/frontend/src/components/CreateBoard/SubTeamsTab/TeamSubTeamsConfigurations.tsx b/frontend/src/components/CreateBoard/SubTeamsTab/TeamSubTeamsConfigurations.tsx
index d872b3f14..58c769a1d 100644
--- a/frontend/src/components/CreateBoard/SubTeamsTab/TeamSubTeamsConfigurations.tsx
+++ b/frontend/src/components/CreateBoard/SubTeamsTab/TeamSubTeamsConfigurations.tsx
@@ -36,7 +36,10 @@ const TeamSubTeamsConfigurations: React.FC = ({
const [stakeholders, setStakeholders] = useState([]);
const [team, setTeam] = useState(null);
- const { data: teams } = useQuery(['teams'], () => getAllTeams(), { suspense: false });
+ const { data: teams } = useQuery(['teams'], () => getAllTeams(), {
+ suspense: false,
+ refetchOnWindowFocus: false
+ });
const setBoardData = useSetRecoilState(createBoardDataState);
const [haveError, setHaveError] = useRecoilState(createBoardError);
@@ -128,16 +131,14 @@ const TeamSubTeamsConfigurations: React.FC = ({
Stakeholders
- {!haveError && stakeholders && stakeholders.length === 1
- ? stakeholders.map(
- ({ firstName, lastName }) => `${firstName} ${lastName}`
- )
- : ''}
- {!haveError && stakeholders && stakeholders.length !== 1
- ? stakeholders.map(
- ({ firstName, lastName }) => `${firstName} ${lastName}, `
- )
- : ''}
+ {!haveError &&
+ stakeholders &&
+ stakeholders.length > 0 &&
+ stakeholders.map((value, index) => {
+ return index < stakeholders.length - 1
+ ? `${value.firstName} ${value.lastName}, `
+ : `${value.firstName} ${value.lastName}`;
+ })}
diff --git a/frontend/src/components/Dashboard/RecentRetros/index.tsx b/frontend/src/components/Dashboard/RecentRetros/index.tsx
index d191899da..dec33fac6 100644
--- a/frontend/src/components/Dashboard/RecentRetros/index.tsx
+++ b/frontend/src/components/Dashboard/RecentRetros/index.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { useInfiniteQuery } from 'react-query';
import { useSetRecoilState } from 'recoil';
@@ -6,6 +6,7 @@ import { getDashboardBoardsRequest } from 'api/boardService';
import { toastState } from 'store/toast/atom/toast.atom';
import { ToastStateEnum } from 'utils/enums/toast-types';
import isEmpty from 'utils/isEmpty';
+import { useSocketBoardIO } from '../../../hooks/useSocketBoardIO';
import EmptyBoards from './partials/EmptyBoards';
import ListOfCards from './partials/ListOfCards';
@@ -38,6 +39,17 @@ const RecentRetros = React.memo(({ userId }) => {
);
const { data, isFetching } = fetchDashboardBoards;
+
+ const teamSocketId = data?.pages[0].boards[0] ? data?.pages[0].boards[0].team._id : undefined;
+
+ // socketId
+ const { socket, queryClient } = useSocketBoardIO(teamSocketId);
+ useEffect(() => {
+ if (!socket) return;
+ socket.on('teamId', () => {
+ queryClient.invalidateQueries('boards/dashboard');
+ });
+ }, [socket, queryClient]);
if (!data || isEmpty(data?.pages[0].boards)) return ;
return (
(({ data, userId, fetchBoards, is
return (
= ({ strategy }) => {
Users
-
-
+
+
Teams
-
+
diff --git a/frontend/src/hooks/useBoard.tsx b/frontend/src/hooks/useBoard.tsx
index ca8537963..0e2e06bef 100644
--- a/frontend/src/hooks/useBoard.tsx
+++ b/frontend/src/hooks/useBoard.tsx
@@ -18,7 +18,7 @@ interface AutoFetchProps {
autoFetchBoard: boolean;
}
-const useBoard = ({ autoFetchBoard }: AutoFetchProps): UseBoardType => {
+const useBoard = ({ autoFetchBoard = false }: AutoFetchProps): UseBoardType => {
const { boardId, queryClient, setToastState } = useBoardUtils();
const setBoard = useSetRecoilState(boardState);
diff --git a/frontend/src/hooks/useCreateBoard.tsx b/frontend/src/hooks/useCreateBoard.tsx
index 70f5535c7..84192b4aa 100644
--- a/frontend/src/hooks/useCreateBoard.tsx
+++ b/frontend/src/hooks/useCreateBoard.tsx
@@ -1,13 +1,13 @@
import { useCallback, useMemo } from 'react';
import { useRecoilState, useResetRecoilState } from 'recoil';
+import { TeamUserRoles } from 'utils/enums/team.user.roles';
import { createBoardDataState } from '../store/createBoard/atoms/create-board.atom';
import { BoardToAdd } from '../types/board/board';
import { BoardUserToAdd } from '../types/board/board.user';
import { Team } from '../types/team/team';
import { TeamUser } from '../types/team/team.user';
import { BoardUserRoles } from '../utils/enums/board.user.roles';
-import { TeamUserRoles } from '../utils/enums/team.user.roles';
const useCreateBoard = (team: Team) => {
const [createBoardData, setCreateBoardData] = useRecoilState(createBoardDataState);
@@ -19,10 +19,9 @@ const useCreateBoard = (team: Team) => {
const MIN_MEMBERS = 4;
const teamMembers = team.users.filter(
- (teamUser) =>
- teamUser.role !== TeamUserRoles.STAKEHOLDER &&
- new Date(teamUser.user.joinedAt).getTime() > 0
+ (teamUser) => teamUser.role !== TeamUserRoles.STAKEHOLDER
);
+
const dividedBoardsCount = board.dividedBoards.length;
const generateSubBoard = useCallback(
diff --git a/frontend/src/hooks/useSocketBoardIO.ts b/frontend/src/hooks/useSocketBoardIO.ts
new file mode 100644
index 000000000..52be76ea9
--- /dev/null
+++ b/frontend/src/hooks/useSocketBoardIO.ts
@@ -0,0 +1,34 @@
+import { useEffect, useState } from 'react';
+import { QueryClient, useQueryClient } from 'react-query';
+import { io, Socket } from 'socket.io-client';
+
+import { NEXT_PUBLIC_BACKEND_URL } from 'utils/constants';
+
+type UseSocketBoardInterface = {
+ socket: Socket | null;
+ queryClient: QueryClient;
+};
+
+export const useSocketBoardIO = (teamId: string | undefined): UseSocketBoardInterface => {
+ const queryClient = useQueryClient();
+ const [socket, setSocket] = useState(null);
+
+ useEffect(() => {
+ const newSocket: Socket = io(NEXT_PUBLIC_BACKEND_URL ?? 'http://127.0.0.1:3200', {
+ transports: ['polling']
+ });
+
+ newSocket.on('connect', () => {
+ newSocket.emit('joinBoards', { teamId });
+ setSocket(newSocket);
+ });
+
+ return () => {
+ newSocket.disconnect();
+ setSocket(null);
+ };
+ }, [teamId, queryClient, setSocket]);
+
+ // return socket?.id;
+ return { socket, queryClient };
+};
diff --git a/frontend/src/pages/board-deleted.tsx b/frontend/src/pages/board-deleted.tsx
new file mode 100644
index 000000000..351b74b34
--- /dev/null
+++ b/frontend/src/pages/board-deleted.tsx
@@ -0,0 +1,47 @@
+import Link from 'next/link';
+
+import {
+ BannerContainer,
+ ContainerSection,
+ GoBackButton,
+ ImageBackground
+} from 'styles/pages/error.styles';
+
+import Banner from 'components/icons/Banner';
+import LogoIcon from 'components/icons/Logo';
+import Text from 'components/Primitives/Text';
+
+export default function Custom404() {
+ return (
+
+
+
+
+
+
+
+
+
+ 404
+
+
+
+ Board deleted
+
+
+ The board was deleted by a board admin
+
+
+
+ Go to Dashboard
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/boards/[boardId].tsx b/frontend/src/pages/boards/[boardId].tsx
index cff72afd4..1a2222adb 100644
--- a/frontend/src/pages/boards/[boardId].tsx
+++ b/frontend/src/pages/boards/[boardId].tsx
@@ -1,6 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react';
import { dehydrate, QueryClient } from 'react-query';
import { GetServerSideProps, NextPage } from 'next';
+import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { useRecoilState, useSetRecoilState } from 'recoil';
@@ -25,9 +26,18 @@ import isEmpty from 'utils/isEmpty';
export const getServerSideProps: GetServerSideProps = async (context) => {
const { boardId } = context.query;
const queryClient = new QueryClient();
- await queryClient.prefetchQuery(['board', { id: boardId }], () =>
- getBoardRequest(boardId as string, context)
- );
+ try {
+ await queryClient.fetchQuery(['board', { id: boardId }], () =>
+ getBoardRequest(boardId as string, context)
+ );
+ } catch (e) {
+ return {
+ redirect: {
+ permanent: false,
+ destination: '/dashboard'
+ }
+ };
+ }
return {
props: {
key: context.query.boardId,
@@ -64,6 +74,7 @@ const Board: NextPage = ({ boardId, mainBoardId }) => {
});
const mainBoard = data?.mainBoardData;
const board = data?.board;
+ const route = useRouter();
// Socket IO Hook
const socketId = useSocketIO(boardId);
@@ -124,8 +135,13 @@ const Board: NextPage = ({ boardId, mainBoardId }) => {
const userIsInBoard = board?.users.find((user) => user.user._id === userId);
- if (!userIsInBoard && !hasAdminRole) return ;
+ useEffect(() => {
+ if (data === null) {
+ route.push('/board-deleted');
+ }
+ }, [data, route]);
+ if (!userIsInBoard && !hasAdminRole) return ;
return board && userId && socketId ? (
<>
diff --git a/frontend/src/types/board/useBoard.ts b/frontend/src/types/board/useBoard.ts
index 607d35608..b943ec440 100644
--- a/frontend/src/types/board/useBoard.ts
+++ b/frontend/src/types/board/useBoard.ts
@@ -10,6 +10,11 @@ export default interface UseBoardType {
UpdateBoardType & { socketId: string },
unknown
>;
- deleteBoard: UseMutationResult;
+ deleteBoard: UseMutationResult<
+ BoardType,
+ unknown,
+ { id: string; socketId?: string; teamId: string },
+ unknown
+ >;
fetchBoard: UseQueryResult;
}
diff --git a/frontend/src/utils/fetchData.tsx b/frontend/src/utils/fetchData.tsx
index d976da0d7..18dc41e6a 100644
--- a/frontend/src/utils/fetchData.tsx
+++ b/frontend/src/utils/fetchData.tsx
@@ -52,12 +52,15 @@ const fetchData = async (url: string, options?: Options): Promise => {
Authorization: refreshToken ? `Bearer ${refreshToken}` : await getToken(context)
};
}
+ try {
+ const { data } = !serverSide
+ ? await instance(instanceOptions)
+ : await serverSideInstance(instanceOptions);
- const { data } = !serverSide
- ? await instance(instanceOptions)
- : await serverSideInstance(instanceOptions);
-
- return data;
+ return data;
+ } catch {
+ return null as any;
+ }
};
export default fetchData;