Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: team participants page #1151

Merged
merged 6 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion backend/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { userRepository } from 'src/modules/users/users.providers';
import { UserRepository } from './../users/repository/user.repository';
import { mongooseBoardUserModule } from './../../infrastructure/database/mongoose.module';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
Expand Down
6 changes: 3 additions & 3 deletions backend/src/modules/auth/services/register.auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { UserRepositoryInterface } from './../../users/repository/user.repository.interface';
import { userRepository } from 'src/modules/users/users.providers';
import { GetTokenAuthService } from 'src/modules/auth/interfaces/services/get-token.auth.service.interface';
import { BOARD_USER_NOT_FOUND, INSERT_FAILED } from 'src/libs/exceptions/messages';
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
Expand Down Expand Up @@ -55,7 +54,7 @@ export default class RegisterAuthServiceImpl implements RegisterAuthService {
throw new BadRequestException(BOARD_USER_NOT_FOUND);
}

const { _id, firstName, lastName } = userFound.user as User;
const { _id, firstName, lastName, isAnonymous } = userFound.user as User;

return {
role: userFound.role,
Expand All @@ -64,7 +63,8 @@ export default class RegisterAuthServiceImpl implements RegisterAuthService {
user: {
_id: String(_id),
firstName,
lastName
lastName,
isAnonymous
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/boards/services/get.board.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export default class GetBoardServiceImpl implements GetBoardServiceInterface {
select: 'user role -board',
populate: {
path: 'user',
select: '_id firstName email lastName'
select: '_id firstName email lastName isAnonymous'
}
})
.lean({ virtuals: true })
Expand Down
4 changes: 2 additions & 2 deletions backend/src/modules/boards/utils/populate-board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const BoardDataPopulate: PopulateOptions[] = [
{
path: 'users',
select: 'user role -board votesCount',
populate: { path: 'user', select: 'firstName email lastName _id' }
populate: { path: 'user', select: 'firstName email lastName _id isAnonymous' }
},
{
path: 'team',
Expand Down Expand Up @@ -49,7 +49,7 @@ export const GetBoardDataPopulate: PopulateOptions[] = [
{
path: 'users',
select: 'user role -board votesCount',
populate: { path: 'user', select: 'firstName email lastName _id' }
populate: { path: 'user', select: 'firstName email lastName _id isAnonymous' }
},
{
path: 'team',
Expand Down
6 changes: 5 additions & 1 deletion backend/src/modules/users/dto/guest.user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsMongoId, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { IsBoolean, IsMongoId, IsNotEmpty, IsOptional, IsString } from 'class-validator';

export default class GuestUserDto {
@ApiProperty()
Expand All @@ -18,4 +18,8 @@ export default class GuestUserDto {
@IsOptional()
@IsString()
lastName?: string;

@IsOptional()
@IsBoolean()
isAnonymous?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,21 @@ type CardBodyProps = {
isCurrentUserResponsible: boolean;
isCurrentUserSAdmin: boolean;
isMemberCurrentUser: boolean;
haveInvalidNumberOfResponsibles: boolean;
responsibleSignedUpUsers: BoardUser[];
isOpen?: boolean;
};

const ParticipantCard = React.memo<CardBodyProps>(
({ member, isCurrentUserResponsible, isCurrentUserSAdmin, isMemberCurrentUser, isOpen }) => {
({
member,
isCurrentUserResponsible,
isCurrentUserSAdmin,
isMemberCurrentUser,
isOpen,
haveInvalidNumberOfResponsibles,
responsibleSignedUpUsers,
}) => {
const {
addAndRemoveBoardParticipants: { mutate },
} = useParticipants();
Expand Down Expand Up @@ -61,6 +71,14 @@ const ParticipantCard = React.memo<CardBodyProps>(

const handleSelectFunction = (checked: boolean) => updateIsResponsibleStatus(checked);

let memberOnlySignedUpResponsible: boolean = false;

if (haveInvalidNumberOfResponsibles) {
memberOnlySignedUpResponsible = !!responsibleSignedUpUsers.find(
(boardUser) => boardUser.user._id === member.user._id,
);
}

return (
<Flex css={{ flex: '1 1 1' }} direction="column">
<Flex>
Expand Down Expand Up @@ -100,6 +118,8 @@ const ParticipantCard = React.memo<CardBodyProps>(
isChecked={isMemberResponsible}
text=""
title="Responsible"
disabledInfo="Select another responsible for the board"
disabled={memberOnlySignedUpResponsible && !isCurrentUserSAdmin}
/>
</Flex>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,33 @@ import { ScrollableContent } from '@/components/Boards/MyBoards/styles';
import { boardParticipantsState } from '@/store/board/atoms/board.atom';
import { BoardUserRoles } from '@/utils/enums/board.user.roles';
import { useSession } from 'next-auth/react';
import { getGuestUserCookies } from '@/utils/getGuestUserCookies';
import ParticipantsLayout from './ParticipantsLayout';
import ParticipantCard from './ParticipantCard.tsx';

const ParticipantsList = () => {
const boardParticipants = useRecoilValue(boardParticipantsState);
const { data: session } = useSession();

// User Id
const userId = getGuestUserCookies() ? getGuestUserCookies().user : session?.user.id;

const isResponsible = !!boardParticipants.find(
(boardUser) =>
boardUser.user._id === session?.user.id && boardUser.role === BoardUserRoles.RESPONSIBLE,
(boardUser) => boardUser.user._id === userId && boardUser.role === BoardUserRoles.RESPONSIBLE,
);

const isSAdmin = !!session?.user.isSAdmin;

const responsiblesList = boardParticipants.filter(
(boardUser) => boardUser.role === BoardUserRoles.RESPONSIBLE,
);

const responsibleSignedUpUsers = responsiblesList.filter(
(boardUser) => !boardUser.user.isAnonymous,
);

const haveInvalidNumberOfResponsibles = responsibleSignedUpUsers.length < 2;

return (
<ParticipantsLayout hasPermissionsToEdit={isResponsible || isSAdmin}>
<Flex direction="column">
Expand All @@ -31,9 +45,11 @@ const ParticipantsList = () => {
<ParticipantCard
key={member.user._id}
member={member}
isMemberCurrentUser={member.user._id === session?.user.id}
isMemberCurrentUser={member.user._id === userId}
isCurrentUserResponsible={isResponsible}
isCurrentUserSAdmin={isSAdmin}
haveInvalidNumberOfResponsibles={haveInvalidNumberOfResponsibles}
responsibleSignedUpUsers={responsibleSignedUpUsers}
/>
))}
</ScrollableContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const HeaderParticipants = ({ isParticipantsPage }: Props) => {
</Flex>
<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Board Creator
Responsibles
</Text>
<AvatarGroup
responsible
Expand Down
111 changes: 11 additions & 100 deletions frontend/src/components/Board/RegularBoard/ReagularHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { useSession } from 'next-auth/react';
import { useRecoilValue } from 'recoil';

import Breadcrumb from '@/components/Primitives/Breadcrumb';
import Icon from '@/components/Primitives/Icon';
import LogoIcon from '@/components/icons/Logo';
import Flex from '@/components/Primitives/Flex';
import Separator from '@/components/Primitives/Separator';
import Text from '@/components/Primitives/Text';
import Tooltip from '@/components/Primitives/Tooltip';
import { boardInfoState } from '@/store/board/atoms/board.atom';
import { BreadcrumbType } from '@/types/board/Breadcrumb';
import { TeamUser } from '@/types/team/team.user';
import { TeamUserRoles } from '@/utils/enums/team.user.roles';
import isEmpty from '@/utils/isEmpty';
import Link from 'next/link';
import { StyledBoardTitle } from '@/components/CardBoard/CardBody/CardTitle/partials/Title/styles';
import AvatarGroup from '@/components/Primitives/Avatar/AvatarGroup';
import {
MergeIconContainer,
RecurrentIconContainer,
Expand All @@ -30,18 +23,11 @@ interface Props {
}

const RegularBoardHeader = ({ isParticipantsPage }: Props) => {
const { data: session } = useSession();

// Atoms
const boardData = useRecoilValue(boardInfoState);

// Get Board Info
const { title, recurrent, users, team, isSubBoard, submitedAt, _id } = boardData.board;

// Get Team users
const teamUsers = team?.users ? team.users : [];

const isRegularBoardWithNoTeam = !team;
const { title, recurrent, isSubBoard, submitedAt, _id } = boardData.board;

// Set breadcrumbs
const breadcrumbItems: BreadcrumbType = isParticipantsPage
Expand Down Expand Up @@ -101,92 +87,17 @@ const RegularBoardHeader = ({ isParticipantsPage }: Props) => {
</TitleSection>
</Flex>
<Flex align="center" gap="24">
{!isEmpty(teamUsers) && (
<Link href={`/teams/${team.id}`}>
<Flex align="center" gap="24">
<Flex align="center" gap="10">
<StyledBoardTitle>
<Text
color="primary800"
size="sm"
fontWeight="medium"
css={{
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
>
{team?.name}
</Text>
</StyledBoardTitle>
<AvatarGroup
listUsers={isSubBoard ? users : teamUsers}
responsible={false}
teamAdmins={false}
userId={session!.user.id}
isClickable
/>
<Flex>
{isParticipantsPage ? (
<HeaderParticipants isParticipantsPage />
) : (
<Link href={`/boards/${_id}/participants`}>
<Flex>
<HeaderParticipants />
</Flex>
{!isEmpty(
teamUsers.filter((user: TeamUser) => user.role === TeamUserRoles.ADMIN),
) && (
<>
<Separator orientation="vertical" size="lg" />

<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Team admins
</Text>
<AvatarGroup
teamAdmins
listUsers={isSubBoard ? users : teamUsers}
responsible={false}
userId={session!.user.id}
isClickable
/>
</Flex>
</>
)}
{!isEmpty(
boardData.board.team?.users.filter(
(user: TeamUser) => user.role === TeamUserRoles.STAKEHOLDER,
),
) && (
<>
<Separator orientation="vertical" size="lg" />

<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Stakeholders
</Text>
<AvatarGroup
stakeholders
listUsers={isSubBoard ? users : teamUsers}
responsible={false}
teamAdmins={false}
userId={session!.user.id}
isClickable
/>
</Flex>
</>
)}
</Flex>
</Link>
)}

{isRegularBoardWithNoTeam && (
<Flex>
{isParticipantsPage ? (
<HeaderParticipants isParticipantsPage />
) : (
<Link href={`/boards/${_id}/participants`}>
<Flex>
<HeaderParticipants />
</Flex>
</Link>
)}
</Flex>
)}
</Link>
)}
</Flex>
</Flex>
</Flex>
</StyledHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const ConfigurationSwitchSettings = ({
disabledInfo,
}: Props) => (
<Flex gap={20}>
{disabledInfo ? (
{disabledInfo && disabled ? (
<Tooltip content={disabledInfo}>
<Flex>
<Switch
Expand All @@ -37,7 +37,12 @@ const ConfigurationSwitchSettings = ({
</Flex>
</Tooltip>
) : (
<Switch checked={isChecked} size="sm" onCheckedChange={handleCheckedChange} />
<Switch
checked={isChecked}
size="sm"
onCheckedChange={handleCheckedChange}
disabled={disabled}
/>
)}
<Flex direction="column">
<Text size="md" fontWeight="medium">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type ListMembersDialogProps = {

const ListMembersDialog = React.memo<ListMembersDialogProps>(
({ usersList, isOpen, setIsOpen, saveUsers, title, btnTitle }) => {
const { data: session } = useSession({ required: true });
const { data: session } = useSession({ required: false });
const [searchMember, setSearchMember] = useState<string>('');

const [usersChecked, setUsersChecked] = useState(usersList);
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/pages/boards/[boardId]/participants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { BoardUserRoles } from '@/utils/enums/board.user.roles';
import { ToastStateEnum } from '@/utils/enums/toast-types';
import { QueryClient, dehydrate, useQuery } from '@tanstack/react-query';
import { GetServerSideProps } from 'next';
import { useSession } from 'next-auth/react';
import React, { Suspense, useCallback, useEffect } from 'react';
import { SetterOrUpdater, useRecoilState, useSetRecoilState } from 'recoil';

Expand All @@ -41,10 +40,9 @@ export const sortParticipantsList = (
};

const BoardParticipants = () => {
const { data: session } = useSession({ required: false });
const setToastState = useSetRecoilState(toastState);
const [boardParticipants, setBoardParticipants] = useRecoilState(boardParticipantsState);
const setRecoilBoard = useSetRecoilState(boardInfoState);
const [recoilBoard, setRecoilBoard] = useRecoilState(boardInfoState);

// Hooks
const {
Expand Down Expand Up @@ -93,8 +91,7 @@ const BoardParticipants = () => {
handleMembersList();
}, [handleMembersList]);

if (!session) return null;
return (
return recoilBoard ? (
<Suspense fallback={<LoadingPage />}>
<QueryError>
<ContentSection gap="36" justify="between">
Expand All @@ -107,6 +104,8 @@ const BoardParticipants = () => {
</ContentSection>
</QueryError>
</Suspense>
) : (
<LoadingPage />
);
};

Expand Down
Loading