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: user card on the list #649

Merged
merged 8 commits into from
Dec 5, 2022
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
7 changes: 3 additions & 4 deletions backend/src/modules/teams/services/get.team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,9 @@ export default class GetTeamService implements GetTeamServiceInterface {
{
$unset: [
'_id',
'user.currentHashedRefreshToken',
'user.isSAdmin',
'user.joinedAt',
'user.isDeleted'
'userWithTeam.currentHashedRefreshToken',
'userWithTeam.joinedAt',
'userWithTeam.isDeleted'
]
},
{
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/api/userService.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { GetServerSidePropsContext } from 'next';

import fetchData from '@/utils/fetchData';
import { User } from '../types/user/user';
import { User, UserWithTeams } from '../types/user/user';

export const getAllUsers = (context?: GetServerSidePropsContext): Promise<User[]> =>
fetchData(`/users`, { context, serverSide: !!context });

export const getAllUsersWithTeams = (
context?: GetServerSidePropsContext,
): Promise<UserWithTeams[]> => fetchData(`/users/teams`, { context, serverSide: !!context });
11 changes: 11 additions & 0 deletions frontend/src/components/Users/UsersList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

import ListOfCards from './partials/ListOfCards';

type UsersWithTeamsProps = {
isFetching: boolean;
};

const TeamsList = ({ isFetching }: UsersWithTeamsProps) => <ListOfCards isLoading={isFetching} />;

export default TeamsList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import { useSession } from 'next-auth/react';

import { styled } from '@/styles/stitches/stitches.config';

import Icon from '@/components/icons/Icon';
import Box from '@/components/Primitives/Box';
import Flex from '@/components/Primitives/Flex';
import Text from '@/components/Primitives/Text';
import { UserWithTeams } from '@/types/user/user';
import SuperAdmin from './SuperAdmin';
import CardEnd from './CardEnd';
import CardTitle from './CardTitle';

const InnerContainer = styled(Flex, Box, {
px: '$32',
backgroundColor: '$white',
borderRadius: '$12',
position: 'relative',
py: '$22',
maxHeight: '$76',
ml: 0,
});

type CardBodyProps = {
userWithTeams: UserWithTeams;
};

const CardBody = React.memo<CardBodyProps>(({ userWithTeams }) => {
const { data: session } = useSession();

const loggedUserIsSAdmin = session?.user.isSAdmin;

const { firstName, lastName, email, isSAdmin } = userWithTeams.user;
const { teamsNames } = userWithTeams;

const getTeamsCountText = () => {
if (teamsNames?.length === 1) {
return 'in 1 team';
}
if (teamsNames?.length !== 0 && teamsNames?.length !== 1 && teamsNames) {
return `in ${teamsNames.length} teams`;
}
return 'no teams';
};

return (
<Flex css={{ flex: '1 1 1', marginBottom: '$10' }} direction="column" gap="12">
<InnerContainer align="center" elevation="1" justify="between">
<Flex align="center" css={{ width: '25%' }} gap="8">
<Icon
name="blob-personal"
css={{
width: '$32',
height: '$32',
zIndex: 1,
}}
/>

<Flex align="center" css={{ width: '$147' }} gap="8">
<CardTitle firstName={firstName} lastName={lastName} />
</Flex>

<Flex align="center" css={{ width: '$147' }}>
<Text color="primary300" size="sm">
{email}
</Text>
</Flex>
</Flex>

<Flex align="center" css={{ justifyContent: 'end', width: '$683' }} gap="8">
<Flex align="center" css={{ ml: '$40', alignItems: 'center' }} gap="8">
<Flex align="center" css={{ width: '$147' }}>
<Text css={{ mr: '$2', fontWeight: '$bold' }} size="sm">
{getTeamsCountText()}
</Text>
</Flex>
</Flex>
<Flex css={{ width: '40%' }} justify="end">
<Flex align="center" css={{ width: '$237' }} justify="start">
{loggedUserIsSAdmin && <SuperAdmin userSAdmin={isSAdmin} />}
</Flex>
{loggedUserIsSAdmin && <CardEnd user={userWithTeams.user} />}
</Flex>
</Flex>
</InnerContainer>
</Flex>
);
});

export default CardBody;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

import Flex from '@/components/Primitives/Flex';
import { User } from '@/types/user/user';
import DeleteUser from './DeleteUser';
import EditUser from './EditUser';

type CardEndProps = {
user: User;
};

const CardEnd: React.FC<CardEndProps> = React.memo(({ user }) => (
<Flex css={{ alignItems: 'center' }}>
<Flex align="center" css={{ ml: '$24' }} gap="24">
<EditUser user={user} />
</Flex>
<Flex align="center" css={{ ml: '$24' }} gap="24">
<DeleteUser userId={user._id} firstName={user.firstName} lastName={user.lastName} />
</Flex>
</Flex>
));

export default CardEnd;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { styled } from '@/styles/stitches/stitches.config';

import Text from '@/components/Primitives/Text';

type CardTitleProps = {
firstName: string;
lastName: string;
};

const StyledBoardTitle = styled(Text, {
fontWeight: '$bold',
fontSize: '$14',
letterSpacing: '$0-17',
'&[data-disabled="true"]': { opacity: 0.4 },
'@hover': {
'&:hover': {
'&[data-disabled="true"]': {
textDecoration: 'none',
cursor: 'default',
},
textDecoration: 'underline',
cursor: 'pointer',
},
},
});

const CardTitle: React.FC<CardTitleProps> = ({ firstName, lastName }) => {
const getTitle = () => (
<StyledBoardTitle>
{firstName} {lastName}
</StyledBoardTitle>
);

return getTitle();
};

export default CardTitle;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Icon from '@/components/icons/Icon';
import AlertCustomDialog from '@/components/Primitives/AlertCustomDialog';
import { AlertDialogTrigger } from '@/components/Primitives/AlertDialog';
import Flex from '@/components/Primitives/Flex';
import Tooltip from '@/components/Primitives/Tooltip';

type DeleteUserProps = { userId: string; firstName: string; lastName: string };

const DeleteUser: React.FC<DeleteUserProps> = ({ firstName, lastName }) => (
<AlertCustomDialog
cancelText="Cancel"
confirmText="Delete"
css={undefined}
defaultOpen={false}
text={`Do you really want to delete the user “${firstName} ${lastName}”?`}
title="Delete User"
>
<Tooltip content="Delete User">
<AlertDialogTrigger asChild>
<Flex pointer>
<Icon
name="trash-alt"
css={{
color: '$primary400',
width: '$20',
height: '$20',
}}
/>
</Flex>
</AlertDialogTrigger>
</Tooltip>
</AlertCustomDialog>
);

export default DeleteUser;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Icon from '@/components/icons/Icon';
import Flex from '@/components/Primitives/Flex';
import Tooltip from '@/components/Primitives/Tooltip';
import { User } from '@/types/user/user';

type EditUserProps = { user: User };

const EditUser: React.FC<EditUserProps> = () => (
<Tooltip content="Edit User">
<Flex pointer>
<Icon
name="edit"
css={{
color: '$primary400',
width: '$20',
height: '$20',
}}
/>
</Flex>
</Tooltip>
);

export default EditUser;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Flex from '@/components/Primitives/Flex';
import Text from '@/components/Primitives/Text';
import { Switch, SwitchThumb } from '@/components/Primitives/Switch';
import Icon from '@/components/icons/Icon';
import { useState } from 'react';

type SuperAdminProps = {
userSAdmin: boolean;
};

const SuperAdmin = ({ userSAdmin }: SuperAdminProps) => {
const [checkedState, setCheckedState] = useState(userSAdmin);

const handleSuperAdminChange = () => {
setCheckedState(!checkedState);
};

return (
<Flex css={{ ml: '$20', display: 'flex', alignItems: 'center' }}>
<Switch checked={checkedState} onCheckedChange={handleSuperAdminChange}>
{checkedState && (
<SwitchThumb>
<Icon
name="check"
css={{
width: '$14',
height: '$14',
color: '$successBase',
}}
/>
</SwitchThumb>
)}
{!checkedState && <SwitchThumb />}
</Switch>
<Text css={{ ml: '$14' }} size="sm" weight="medium">
Super Admin
</Text>
</Flex>
);
};
export default SuperAdmin;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';

import { DotsLoading } from '@/components/loadings/DotsLoading';
import Flex from '@/components/Primitives/Flex';
import { UserWithTeams } from '@/types/user/user';
import Text from '@/components/Primitives/Text';
import SearchInput from '@/components/Teams/CreateTeam/ListMembers/SearchInput';
import { useRecoilValue } from 'recoil';
import { usersWithTeamsState } from '@/store/user/atoms/user.atom';
import { ScrollableContent } from '../../../../Boards/MyBoards/styles';
import CardBody from '../CardUser/CardBody';

type ListOfCardsProp = {
isLoading: boolean;
};

const ListOfCards = React.memo<ListOfCardsProp>(({ isLoading }) => {
const usersWithTeams = useRecoilValue(usersWithTeamsState);
return (
<>
<Flex>
<Text css={{ fontWeight: '$bold', flex: 1, mt: '$36' }}>
{usersWithTeams?.length} registered users
</Text>
<Flex css={{ width: '460px' }} direction="column" gap={16}>
<SearchInput icon="search" iconPosition="left" id="search" placeholder="Search user" />
</Flex>
</Flex>
<ScrollableContent direction="column" gap="24" justify="start" css={{ mt: '-1px' }}>
<Flex direction="column">
{usersWithTeams?.map((user: UserWithTeams) => (
<CardBody key={user.user._id} userWithTeams={user} />
))}
</Flex>

{isLoading && (
<Flex justify="center">
<DotsLoading />
</Flex>
)}
</ScrollableContent>
</>
);
});

export default ListOfCards;
Loading