refactor: user profile component decomposition

This commit is contained in:
Sergey Yarkov 2022-02-01 21:49:05 +03:00
parent c80ef877c5
commit 20066ec1ba
10 changed files with 203 additions and 63 deletions

View File

@ -0,0 +1,20 @@
import React from 'react';
import { Flex, Avatar as ChakraAvatar, AvatarBadge as ChakraAvatarBadge, useColorModeValue } from '@chakra-ui/react';
import type { IAvatarProps } from '.';
const Avatar: React.FC<IAvatarProps> = props => {
const { name, isOnline } = props;
const onlineColor = isOnline ? 'green.500' : 'red.500';
const borderColor = useColorModeValue('#E2E8F0', '#2D3748');
return (
<Flex flexDir={'column'} alignItems={'center'}>
<ChakraAvatar border={`4px solid ${borderColor}`} borderColor={borderColor} name={name} size='2xl'>
<ChakraAvatarBadge boxSize='0.75em' bg={onlineColor} />
</ChakraAvatar>
{props.children}
</Flex>
);
};
export { Avatar };

View File

@ -0,0 +1,19 @@
import React from 'react';
import { Button, ButtonProps } from '@chakra-ui/react';
import { MdOutlineMessage } from 'react-icons/md';
import { useHistory } from 'react-router-dom';
import type { IChatButtonProps } from '.';
const ChatButton: React.FC<IChatButtonProps & ButtonProps> = props => {
const { userid } = props;
const history = useHistory();
const handleChat = () => history.push(`/chat/${userid}`);
return (
<Button onClick={handleChat} mt='5' variant={'outline'} size={'sm'} leftIcon={<MdOutlineMessage />} {...props}>
Chat
</Button>
);
};
export { ChatButton };

View File

@ -0,0 +1,13 @@
import { Avatar } from './Avatar';
import { ChatButton } from './ChatButton';
export interface IAvatarProps {
name: string;
isOnline: boolean;
}
export interface IChatButtonProps {
userid: string;
}
export { Avatar, ChatButton };

View File

@ -0,0 +1,16 @@
import React from 'react';
import { Box, Text } from '@chakra-ui/react';
import type { IAboutProps } from '.';
const About: React.FC<IAboutProps> = () => {
return (
<Box>
<Text fontSize={'sm'}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam sapiente quisquam vero harum ipsam officiis
rerum. Cum, excepturi nulla.
</Text>
</Box>
);
};
export { About };

View File

@ -0,0 +1,30 @@
import React from 'react';
import { Flex, Text, Icon, Divider } from '@chakra-ui/react';
import { CalendarIcon, RepeatClockIcon } from '@chakra-ui/icons';
import type { IDetailsProps } from '.';
const Details: React.FC<IDetailsProps> = ({ registered, lastLogin }) => {
return (
<>
<Flex my={'3'} justifyContent={{ base: 'center', md: 'flex-start' }}>
<Flex>
<Flex alignItems={'center'} mr='5'>
<Icon as={CalendarIcon} color='gray.500' />
<Text fontWeight={'medium'} fontSize={'sm'} color='gray.500' ml='3'>
Joined: {new Date(registered).toLocaleDateString()}
</Text>
</Flex>
<Flex alignItems={'center'} mr='5'>
<Icon as={RepeatClockIcon} color='gray.500' />
<Text fontWeight={'medium'} fontSize={'sm'} color='gray.500' ml='3'>
Last login: {lastLogin}
</Text>
</Flex>
</Flex>
</Flex>
<Divider my='3' />
</>
);
};
export { Details };

View File

@ -0,0 +1,19 @@
import React from 'react';
import UserBadge from '@educt/components/UserBadge';
import { Flex, Box, Heading as ChakraHeading } from '@chakra-ui/react';
import type { IHeadingProps } from '.';
const Heading: React.FC<IHeadingProps> = ({ fullname, roles }) => {
return (
<Flex justifyContent={{ base: 'center', md: 'flex-start' }} mt={{ base: '5', md: '0' }}>
<ChakraHeading as='h2' fontSize={'xl'} fontWeight={'bold'}>
{fullname}&nbsp;&bull;&nbsp;
</ChakraHeading>
<Box>
<UserBadge roles={roles} />
</Box>
</Flex>
);
};
export { Heading };

View File

@ -0,0 +1,12 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
const Info: React.FC = props => {
return (
<Box ml={{ base: '0', md: '10' }} {...props}>
{props.children}
</Box>
);
};
export { Info };

View File

@ -0,0 +1,21 @@
import { IUser } from '@educt/interfaces';
import { Info } from './Info';
import { Heading } from './Heading';
import { Details } from './Details';
import { About } from './About';
export interface IHeadingProps {
fullname: string;
roles: IUser['roles'];
}
export interface IDetailsProps {
registered: string;
lastLogin: string;
}
export interface IAboutProps {
about: string;
}
export { Info, Heading, Details, About };

View File

@ -0,0 +1,29 @@
import React, { createContext } from 'react';
import { BoxProps, Flex } from '@chakra-ui/react';
import { IUser } from '@educt/interfaces';
import { Avatar, ChatButton, IAvatarProps, IChatButtonProps } from './Avatar';
import { About, Details, Heading, IAboutProps, IDetailsProps, IHeadingProps, Info } from './Info';
interface IUserProfileComposition {
Avatar: React.FC<IAvatarProps>;
ChatButton: React.FC<IChatButtonProps>;
Info: React.FC<BoxProps>;
Heading: React.FC<IHeadingProps>;
Details: React.FC<IDetailsProps>;
About: React.FC<IAboutProps>;
}
export const UserProfileContext = createContext<IUser | undefined>(undefined);
const UserProfile: React.FC & IUserProfileComposition = props => {
return <Flex flexDir={{ base: 'column', md: 'row' }}>{props.children}</Flex>;
};
UserProfile.Avatar = Avatar;
UserProfile.ChatButton = ChatButton;
UserProfile.Info = Info;
UserProfile.Heading = Heading;
UserProfile.Details = Details;
UserProfile.About = About;
export { UserProfile };

View File

@ -1,29 +1,19 @@
import React from 'react';
import { PageContent, PageWrapper } from '@educt/components/PageElements';
import { IPageProps } from '@educt/interfaces';
import { useFetchUser } from '@educt/hooks/queries/user/useFetchUser';
import { Redirect, useParams } from 'react-router-dom';
import { Redirect } from 'react-router-dom';
import LoadingPage from '@educt/components/LoadingPage';
import {
Box,
Button,
Flex,
Avatar,
Text,
Heading,
useColorModeValue,
Icon,
AvatarBadge,
Divider,
} from '@chakra-ui/react';
import { MdOutlineMessage } from 'react-icons/md';
import UserBadge from '@educt/components/UserBadge';
import { CalendarIcon, RepeatClockIcon } from '@chakra-ui/icons';
import { PageContent, PageWrapper } from '@educt/components/PageElements';
import { UserProfile } from '@educt/components/UserProfile';
import type { IPageProps } from '@educt/interfaces';
/**
* Hooks
*/
import { useParams } from 'react-router-dom';
import { useFetchUser } from '@educt/hooks/queries/user/useFetchUser';
const UserPage: React.FC<IPageProps> = () => {
const { id } = useParams<{ id: string }>();
const { data: user, error, isLoading } = useFetchUser(id);
const color = useColorModeValue('#E2E8F0', '#2D3748');
if (error?.response?.status === 404) return <Redirect to='/404' />;
@ -32,49 +22,20 @@ const UserPage: React.FC<IPageProps> = () => {
return (
<PageWrapper maxW='800px'>
<PageContent>
<Flex>
<Flex flexDir={'column'} alignItems={'center'}>
<Avatar border={`4px solid ${color}`} borderColor={color} name={user.fullname} size='2xl'>
<AvatarBadge boxSize='0.75em' bg='green.500' />
</Avatar>
<Button mt='5' variant={'outline'} size={'sm'} leftIcon={<MdOutlineMessage />}>
Message
</Button>
</Flex>
<Box ml='10'>
<Flex>
<Heading as='h2' fontSize={'xl'} fontWeight={'bold'}>
{user.fullname}&nbsp;&bull;&nbsp;
</Heading>
<Box>
<UserBadge roles={user.roles} />
</Box>
</Flex>
<Flex my={'3'}>
<Flex>
<Flex alignItems={'center'} mr='5'>
<Icon as={CalendarIcon} color='gray.500' />
<Text fontWeight={'medium'} fontSize={'sm'} color='gray.500' ml='1'>
Registered: {new Date(user.created_at).toLocaleDateString()}
</Text>
</Flex>
<Flex alignItems={'center'} mr='5'>
<Icon as={RepeatClockIcon} color='gray.500' />
<Text fontWeight={'medium'} fontSize={'sm'} color='gray.500' ml='1'>
Last login: 3 min ago
</Text>
</Flex>
</Flex>
</Flex>
<Divider my='3' />
<Box>
<Text fontSize={'sm'}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam sapiente quisquam vero harum ipsam
officiis rerum. Cum, excepturi nulla.
</Text>
</Box>
</Box>
</Flex>
<UserProfile>
<UserProfile.Avatar name={user.fullname} isOnline>
<UserProfile.ChatButton userid={user.id} />
</UserProfile.Avatar>
<UserProfile.Info>
<UserProfile.Heading fullname={user.fullname} roles={user.roles} />
<UserProfile.Details registered={user.created_at} lastLogin='3 days ago' />
<UserProfile.About
about={
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla asperiores consectetur quasi minima veritatis id quisquam natus in nostrum quam?'
}
/>
</UserProfile.Info>
</UserProfile>
</PageContent>
</PageWrapper>
);