Merge pull request #113 from anik-ghosh-au7/fix/dashboard

Fix/dashboard
This commit is contained in:
Lakhan Samani 2022-01-31 14:30:32 +05:30 committed by GitHub
commit 2a91f3e7d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 341 additions and 201 deletions

View File

@ -0,0 +1,112 @@
import React from 'react';
import {
Button,
Center,
Flex,
MenuItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
Text,
useToast,
} from '@chakra-ui/react';
import { useClient } from 'urql';
import { FaRegTrashAlt } from 'react-icons/fa';
import { DeleteUser } from '../graphql/mutation';
import { capitalizeFirstLetter } from '../utils';
interface userDataTypes {
id: string;
email: string;
}
const DeleteUserModal = ({
user,
updateUserList,
}: {
user: userDataTypes;
updateUserList: Function;
}) => {
const client = useClient();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const [userData, setUserData] = React.useState<userDataTypes>({
id: '',
email: '',
});
React.useEffect(() => {
setUserData(user);
}, []);
const deleteHandler = async () => {
const res = await client
.mutation(DeleteUser, { params: { email: userData.email } })
.toPromise();
if (res.error) {
toast({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
});
return;
} else if (res.data?._delete_user) {
toast({
title: capitalizeFirstLetter(res.data?._delete_user.message),
isClosable: true,
status: 'success',
position: 'bottom-right',
});
}
onClose();
updateUserList();
};
return (
<>
<MenuItem onClick={onOpen}>Delete User</MenuItem>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Delete User</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text fontSize="md">Are you sure?</Text>
<Flex
padding="5%"
marginTop="5%"
marginBottom="2%"
border="1px solid #ff7875"
borderRadius="5px"
flexDirection="column"
>
<Text fontSize="sm">
User <b>{user.email}</b> will be deleted permanently!
</Text>
</Flex>
</ModalBody>
<ModalFooter>
<Button
leftIcon={<FaRegTrashAlt />}
colorScheme="red"
variant="solid"
onClick={deleteHandler}
isDisabled={false}
>
<Center h="100%" pt="5%">
Delete
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default DeleteUserModal;

View File

@ -23,9 +23,7 @@ export const AdminLogout = `
`; `;
export const UpdateEnvVariables = ` export const UpdateEnvVariables = `
mutation updateEnvVariables( mutation updateEnvVariables($params: UpdateEnvInput!) {
$params: UpdateEnvInput!
) {
_update_env(params: $params) { _update_env(params: $params) {
message message
} }
@ -33,11 +31,17 @@ export const UpdateEnvVariables = `
`; `;
export const UpdateUser = ` export const UpdateUser = `
mutation updateUser( mutation updateUser($params: UpdateUserInput!) {
$params: UpdateUserInput!
) {
_update_user(params: $params) { _update_user(params: $params) {
id id
} }
} }
`; `;
export const DeleteUser = `
mutation deleteUser($params: DeleteUserInput!) {
_delete_user(params: $params) {
message
}
}
`;

View File

@ -28,6 +28,7 @@ import {
MenuList, MenuList,
MenuItem, MenuItem,
useToast, useToast,
Spinner,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import {
FaAngleLeft, FaAngleLeft,
@ -40,6 +41,7 @@ import {
import { UserDetailsQuery } from '../graphql/queries'; import { UserDetailsQuery } from '../graphql/queries';
import { UpdateUser } from '../graphql/mutation'; import { UpdateUser } from '../graphql/mutation';
import EditUserModal from '../components/EditUserModal'; import EditUserModal from '../components/EditUserModal';
import DeleteUserModal from '../components/DeleteUserModal';
interface paginationPropTypes { interface paginationPropTypes {
limit: number; limit: number;
@ -98,7 +100,9 @@ export default function Users() {
maxPages: 1, maxPages: 1,
}); });
const [userList, setUserList] = React.useState<userDataTypes[]>([]); const [userList, setUserList] = React.useState<userDataTypes[]>([]);
const [loading, setLoading] = React.useState<boolean>(false);
const updateUserList = async () => { const updateUserList = async () => {
setLoading(true);
const { data } = await client const { data } = await client
.query(UserDetailsQuery, { .query(UserDetailsQuery, {
params: { params: {
@ -112,9 +116,21 @@ export default function Users() {
if (data?._users) { if (data?._users) {
const { pagination, users } = data._users; const { pagination, users } = data._users;
const maxPages = getMaxPages(pagination); const maxPages = getMaxPages(pagination);
setPaginationProps({ ...paginationProps, ...pagination, maxPages }); if (users && users.length > 0) {
setUserList(users); setPaginationProps({ ...paginationProps, ...pagination, maxPages });
setUserList(users);
} else {
if (paginationProps.page !== 1) {
setPaginationProps({
...paginationProps,
...pagination,
maxPages,
page: 1,
});
}
}
} }
setLoading(false);
}; };
React.useEffect(() => { React.useEffect(() => {
updateUserList(); updateUserList();
@ -162,208 +178,216 @@ export default function Users() {
Users Users
</Text> </Text>
</Flex> </Flex>
{userList.length > 0 ? ( {!loading ? (
<Table variant="simple"> userList.length > 0 ? (
<Thead> <Table variant="simple">
<Tr> <Thead>
<Th>Email</Th> <Tr>
<Th>Created At</Th> <Th>Email</Th>
<Th>Signup Methods</Th> <Th>Created At</Th>
<Th>Roles</Th> <Th>Verified</Th>
<Th>Verified</Th> <Th>Actions</Th>
<Th>Actions</Th> </Tr>
</Tr> </Thead>
</Thead> <Tbody>
<Tbody> {userList.map((user: userDataTypes) => {
{userList.map((user: userDataTypes) => { const { email_verified, created_at, ...rest }: any = user;
const { email_verified, created_at, ...rest }: any = user; return (
return ( <Tr key={user.id} style={{ fontSize: 14 }}>
<Tr key={user.id} style={{ fontSize: 14 }}> <Td>{user.email}</Td>
<Td>{user.email}</Td> <Td>{dayjs(user.created_at).format('MMM DD, YYYY')}</Td>
<Td> <Td>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')} <Tag
</Td> size="sm"
<Td>{user.signup_methods}</Td> variant="outline"
<Td>{user.roles.join(', ')}</Td> colorScheme={user.email_verified ? 'green' : 'yellow'}
<Td> >
<Tag {user.email_verified.toString()}
size="sm" </Tag>
variant="outline" </Td>
colorScheme={user.email_verified ? 'green' : 'yellow'} <Td>
> <Menu>
{user.email_verified.toString()} <MenuButton as={Button} variant="unstyled" size="sm">
</Tag> <Flex
</Td> justifyContent="space-between"
<Td> alignItems="center"
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
>
<Text fontSize="sm" fontWeight="light">
Menu
</Text>
<FaAngleDown style={{ marginLeft: 10 }} />
</Flex>
</MenuButton>
<MenuList>
<EditUserModal
user={rest}
updateUserList={updateUserList}
/>
{!user.email_verified && (
<MenuItem
onClick={() => userVerificationHandler(user)}
> >
Verify User <Text fontSize="sm" fontWeight="light">
</MenuItem> Menu
)} </Text>
</MenuList> <FaAngleDown style={{ marginLeft: 10 }} />
</Menu> </Flex>
</Td> </MenuButton>
</Tr> <MenuList>
); {!user.email_verified && (
})} <MenuItem
</Tbody> onClick={() => userVerificationHandler(user)}
{paginationProps.maxPages > 1 && ( >
<TableCaption> Verify User
<Flex justifyContent="space-between" alignItems="center" m="2% 0"> </MenuItem>
<Flex flex="1"> )}
<Tooltip label="First Page"> <EditUserModal
<IconButton user={rest}
aria-label="icon button" updateUserList={updateUserList}
onClick={() => />
<DeleteUserModal
user={rest}
updateUserList={updateUserList}
/>
</MenuList>
</Menu>
</Td>
</Tr>
);
})}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: 1,
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({ paginationHandler({
page: 1, page: 1,
limit: parseInt(e.target.value),
}) })
} }
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
> >
<NumberInputField /> {getLimits(paginationProps).map((pageSize) => (
<NumberInputStepper> <option key={pageSize} value={pageSize}>
<NumberIncrementStepper /> Show {pageSize}
<NumberDecrementStepper /> </option>
</NumberInputStepper> ))}
</NumberInput> </Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex> </Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
>
{getLimits(paginationProps).map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex> </Flex>
<Flex flex="1"> </TableCaption>
<Tooltip label="Next Page"> )}
<IconButton </Table>
aria-label="icon button" ) : (
onClick={() => <Flex
paginationHandler({ flexDirection="column"
page: paginationProps.page + 1, minH="25vh"
}) justifyContent="center"
} alignItems="center"
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
</Flex>
</TableCaption>
)}
</Table>
) : (
<Flex
flexDirection="column"
minH="25vh"
justifyContent="center"
alignItems="center"
>
<Center w="50px" marginRight="1.5%">
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
</Center>
<Text
fontSize="2xl"
paddingRight="1%"
fontWeight="bold"
color="#d9d9d9"
> >
No Data <Center w="50px" marginRight="1.5%">
</Text> <FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
</Flex> </Center>
<Text
fontSize="2xl"
paddingRight="1%"
fontWeight="bold"
color="#d9d9d9"
>
No Data
</Text>
</Flex>
)
) : (
<Center minH="25vh">
<Spinner />
</Center>
)} )}
</Box> </Box>
); );