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

feat: personal boards page #857

Merged
merged 10 commits into from
Jan 16, 2023
2 changes: 1 addition & 1 deletion frontend/src/components/Board/DragDropArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { DragDropContext, DropResult, BeforeCapture } from '@hello-pangea/dnd';

import Column from '@/components/Board/Column/Column';
import Flex from '@/components/Primitives/Flex';
import { countBoardCards } from '@/helper/board/countCards';
import useCards from '@/hooks/useCards';
Expand All @@ -13,6 +12,7 @@ import UpdateCardPositionDto from '@/types/card/updateCardPosition.dto';
import { ToastStateEnum } from '@/utils/enums/toast-types';
import { onDragCardStart } from '@/store/card/atoms/card.atom';
import { filteredColumnsState } from '@/store/board/atoms/filterColumns';
import Column from '@/components/Board/Column/Column';

type Props = {
userId: string;
Expand Down
197 changes: 197 additions & 0 deletions frontend/src/components/Board/RegularBoard/ReagularHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { useSession } from 'next-auth/react';
import { useRecoilValue } from 'recoil';

import Breadcrumb from '@/components/breadcrumb/Breadcrumb';
import CardAvatars from '@/components/CardBoard/CardAvatars';
import Icon from '@/components/icons/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 {
MergeIconContainer,
RecurrentIconContainer,
StyledHeader,
StyledLogo,
TitleSection,
} from '../../SplitBoard/Header/styles';

const RegularBoardHeader = () => {
const { data: session } = useSession({ required: true });

// Atoms
const boardData = useRecoilValue(boardInfoState);

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

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

const isPersonalBoard = !team && users.length <= 1;

const isRegularBoardWithNoTeam = !team && users.length > 1;

// Set breadcrumbs
const breadcrumbItems: BreadcrumbType = [
{
title: 'Boards',
link: '/boards',
},
{
title,
isActive: true,
},
];

return (
<StyledHeader>
<Flex align="center" gap="20" justify="between">
<Flex direction="column">
<Flex align="center" gap={!isSubBoard ? 26 : undefined}>
<Breadcrumb items={breadcrumbItems} />
</Flex>
<TitleSection>
<StyledLogo>
<LogoIcon />
</StyledLogo>
<Text heading="2">{title}</Text>

{recurrent && (
<Tooltip content="Occurs every month">
<RecurrentIconContainer>
<Icon name="recurring" />
</RecurrentIconContainer>
</Tooltip>
)}

{isSubBoard && !submitedAt && (
<Tooltip content="Unmerged">
<MergeIconContainer isMerged={!!submitedAt}>
<Icon name="merge" />
</MergeIconContainer>
</Tooltip>
)}
</TitleSection>
</Flex>
<Flex align="center" gap="24">
<Flex align="center" gap="10">
<Text
color="primary800"
size="sm"
css={{
fontWeight: 500,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
>
{team?.name}
</Text>
<CardAvatars
isBoardsPage
listUsers={isSubBoard ? users : teamUsers}
responsible={false}
teamAdmins={false}
userId={session!.user.id}
/>
</Flex>
{!isEmpty(teamUsers.filter((user: TeamUser) => user.role === TeamUserRoles.ADMIN)) && (
<>
<Separator css={{ height: '$24 !important' }} data-orientation="vertical" />

<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Team admins
</Text>
<CardAvatars
isBoardsPage
teamAdmins
listUsers={isSubBoard ? users : teamUsers}
responsible={false}
userId={session!.user.id}
/>
</Flex>
</>
)}
{!isEmpty(
boardData.board.team?.users.filter(
(user: TeamUser) => user.role === TeamUserRoles.STAKEHOLDER,
),
) && (
<>
<Separator css={{ height: '$24 !important' }} data-orientation="vertical" />

<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Stakeholders
</Text>
<CardAvatars
isBoardsPage
stakeholders
listUsers={isSubBoard ? users : teamUsers}
responsible={false}
teamAdmins={false}
userId={session!.user.id}
/>
</Flex>
</>
)}
{isRegularBoardWithNoTeam && (
<>
<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Participants
</Text>
<CardAvatars
isBoardsPage
responsible={false}
listUsers={users}
teamAdmins={false}
userId={session!.user.id}
/>
</Flex>
<Separator css={{ height: '$24 !important' }} data-orientation="vertical" />
<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Board Creator
</Text>
<CardAvatars
isBoardsPage
responsible
listUsers={users}
teamAdmins={false}
userId={session!.user.id}
/>
</Flex>
</>
)}
{isPersonalBoard && (
<>
<Flex align="center" gap="10">
<Text color="primary300" size="sm">
Board Creator
</Text>
<CardAvatars
isBoardsPage
responsible
listUsers={users}
teamAdmins={false}
userId={session!.user.id}
/>
</Flex>
</>
)}
</Flex>
</Flex>
</StyledHeader>
);
};

export default RegularBoardHeader;
107 changes: 107 additions & 0 deletions frontend/src/components/Board/RegularBoard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React, { useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';

import { Container } from '@/styles/pages/boards/board.styles';

import DragDropArea from '@/components/Board/DragDropArea';
import LoadingPage from '@/components/loadings/LoadingPage';
import Flex from '@/components/Primitives/Flex';
import { useSocketIO } from '@/hooks/useSocketIO';
import { boardInfoState } from '@/store/board/atoms/board.atom';
import { BoardUserRoles } from '@/utils/enums/board.user.roles';
import Button from '@/components/Primitives/Button';
import Icon from '@/components/icons/Icon';
import { BoardSettings } from '@/components/Board/SplitBoard/Settings';
import { useSession } from 'next-auth/react';
import RegularBoardHeader from './ReagularHeader';

const RegularBoard = () => {
// States
// State or open and close Board Settings Dialog
const [isOpen, setIsOpen] = useState(false);

// Recoil States
const { board } = useRecoilValue(boardInfoState);

// Session Details
const { data: session } = useSession({ required: true });

const userId = session?.user.id;

// Socket IO Hook
const socketId = useSocketIO(board?._id);

// Board Settings permissions
const isStakeholderOrAdmin = useMemo(
() =>
board.users.some(
(boardUser) =>
[BoardUserRoles.STAKEHOLDER, BoardUserRoles.RESPONSIBLE].includes(boardUser.role) &&
boardUser.user._id === userId,
),
[board.users, userId],
);

const [isResponsible, isOwner] = useMemo(
() =>
board
? [
board.users.some(
(boardUser) =>
boardUser.role === BoardUserRoles.RESPONSIBLE && boardUser.user._id === userId,
),
board.createdBy._id === userId,
]
: [false, false],
[board, userId],
);

// Show board settings button if current user is allowed to edit
const hasAdminRole = isStakeholderOrAdmin || session?.user.isSAdmin || isOwner || isResponsible;

const userIsInBoard = useMemo(
() => board.users.find((user) => user.user._id === userId),
[board.users, userId],
);

const handleOpen = () => {
setIsOpen(true);
};

if (!userIsInBoard && !hasAdminRole) return <LoadingPage />;
return board && userId && socketId ? (
<>
<RegularBoardHeader />
<Container direction="column">
<Flex gap={40} align="center" css={{ py: '$32', width: '100%' }} justify="between">
{hasAdminRole && !board?.submitedAt && (
<>
<Button onClick={handleOpen} variant="primaryOutline">
<Icon name="settings" />
Board settings
<Icon name="arrow-down" />
</Button>
{isOpen && (
<BoardSettings
isOpen={isOpen}
isOwner={isOwner}
isResponsible={isResponsible}
isSAdmin={session?.user.isSAdmin}
isStakeholderOrAdmin={isStakeholderOrAdmin}
setIsOpen={setIsOpen}
socketId={socketId}
/>
)}
</>
)}
</Flex>

<DragDropArea board={board} socketId={socketId} userId={userId} />
</Container>
</>
) : (
<LoadingPage />
);
};

export default RegularBoard;
2 changes: 1 addition & 1 deletion frontend/src/components/Primitives/Dialog/DialogFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ButtonsContainer } from '@/components/Board/Settings/styles';
import { ButtonsContainer } from '@/components/Board/SplitBoard/Settings/styles';
import Button from '../Button';

type FooterProps = {
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/Primitives/Dialog/DialogHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { StyledDialogCloseButton, StyledDialogTitle } from '@/components/Board/Settings/styles';
import {
StyledDialogCloseButton,
StyledDialogTitle,
} from '@/components/Board/SplitBoard/Settings/styles';
import Icon from '@/components/icons/Icon';
import { DialogClose } from '@radix-ui/react-dialog';
import Flex from '../Flex';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Primitives/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
StyledDialogContainer,
StyledDialogContent,
StyledDialogOverlay,
} from '@/components/Board/Settings/styles';
} from '@/components/Board/SplitBoard/Settings/styles';
import DialogFooter from './DialogFooter';
import DialogHeader from './DialogHeader';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Icon from '@/components/icons/Icon';
import { membersListState } from '@/store/team/atom/team.atom';

import Tooltip from '@/components/Primitives/Tooltip';
import { ConfigurationSettings } from '@/components/Board/Settings/partials/ConfigurationSettings';
import { ConfigurationSettings } from '@/components/Board/SplitBoard/Settings/partials/ConfigurationSettings';
import CardEndTeam from '@/components/Teams/Team/CardEnd';

import useTeam from '@/hooks/useTeam';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
StyledDialogContent,
StyledDialogOverlay,
StyledDialogTitle,
} from '@/components/Board/Settings/styles';
} from '@/components/Board/SplitBoard/Settings/styles';
import Flex from '@/components/Primitives/Flex';
import Checkbox from '@/components/Primitives/Checkbox';
import Button from '@/components/Primitives/Button';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Flex from '@/components/Primitives/Flex';
import Text from '@/components/Primitives/Text';
import { useState } from 'react';
import { ConfigurationSettings } from '@/components/Board/Settings/partials/ConfigurationSettings';

import useUser from '@/hooks/useUser';
import { UpdateUserIsAdmin } from '@/types/user/user';
import { ConfigurationSettings } from '@/components/Board/SplitBoard/Settings/partials/ConfigurationSettings';

type SuperAdminProps = {
userSAdmin: boolean;
Expand Down
Loading