Merge pull request #111 from anik-ghosh-au7/feat/manage-users
Feat/manage users
This commit is contained in:
commit
ea14cc1743
2315
dashboard/package-lock.json
generated
2315
dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -17,6 +17,7 @@
|
|||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-router-dom": "^5.3.2",
|
||||
"dayjs": "^1.10.7",
|
||||
"esbuild": "^0.14.9",
|
||||
"framer-motion": "^5.5.5",
|
||||
"graphql": "^16.2.0",
|
||||
|
|
250
dashboard/src/components/EditUserModal.tsx
Normal file
250
dashboard/src/components/EditUserModal.tsx
Normal file
|
@ -0,0 +1,250 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
Flex,
|
||||
MenuItem,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Stack,
|
||||
useDisclosure,
|
||||
Text,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { useClient } from 'urql';
|
||||
import { FaSave } from 'react-icons/fa';
|
||||
import InputField from './InputField';
|
||||
import {
|
||||
ArrayInputType,
|
||||
DateInputType,
|
||||
SelectInputType,
|
||||
TextInputType,
|
||||
} from '../constants';
|
||||
import { getObjectDiff } from '../utils';
|
||||
import { UpdateUser } from '../graphql/mutation';
|
||||
|
||||
const GenderTypes = {
|
||||
Undisclosed: null,
|
||||
Male: 'Male',
|
||||
Female: 'Female',
|
||||
};
|
||||
|
||||
interface userDataTypes {
|
||||
id: string;
|
||||
email: string;
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
middle_name: string;
|
||||
nickname: string;
|
||||
gender: string;
|
||||
birthdate: string;
|
||||
phone_number: string;
|
||||
picture: string;
|
||||
roles: [string] | [];
|
||||
}
|
||||
|
||||
const EditUserModal = ({
|
||||
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: '',
|
||||
given_name: '',
|
||||
family_name: '',
|
||||
middle_name: '',
|
||||
nickname: '',
|
||||
gender: '',
|
||||
birthdate: '',
|
||||
phone_number: '',
|
||||
picture: '',
|
||||
roles: [],
|
||||
});
|
||||
React.useEffect(() => {
|
||||
setUserData(user);
|
||||
}, []);
|
||||
const saveHandler = async () => {
|
||||
const diff = getObjectDiff(user, userData);
|
||||
const updatedUserData = diff.reduce(
|
||||
(acc: any, property: string) => ({
|
||||
...acc,
|
||||
// @ts-ignore
|
||||
[property]: userData[property],
|
||||
}),
|
||||
{}
|
||||
);
|
||||
const res = await client
|
||||
.mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } })
|
||||
.toPromise();
|
||||
if (res.error) {
|
||||
toast({
|
||||
title: 'User data update failed',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
} else if (res.data?._update_user?.id) {
|
||||
toast({
|
||||
title: 'User data update successful',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
updateUserList();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MenuItem onClick={onOpen}>Edit User Details</MenuItem>
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Edit User Details</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Stack>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Given Name:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={TextInputType.GIVEN_NAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Middle Name:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={TextInputType.MIDDLE_NAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Family Name:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={TextInputType.FAMILY_NAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Birth Date:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={DateInputType.BIRTHDATE}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Nickname:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={TextInputType.NICKNAME}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Gender:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={SelectInputType.GENDER}
|
||||
value={userData.gender}
|
||||
options={GenderTypes}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Phone Number:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={TextInputType.PHONE_NUMBER}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Picture:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={TextInputType.PICTURE}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Roles:</Text>
|
||||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
variables={userData}
|
||||
setVariables={setUserData}
|
||||
inputType={ArrayInputType.USER_ROLES}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
leftIcon={<FaSave />}
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
onClick={saveHandler}
|
||||
isDisabled={false}
|
||||
>
|
||||
<Center h="100%" pt="5%">
|
||||
Save
|
||||
</Center>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditUserModal;
|
|
@ -29,13 +29,14 @@ import {
|
|||
TextInputType,
|
||||
TextAreaInputType,
|
||||
SwitchInputType,
|
||||
DateInputType,
|
||||
} from '../constants';
|
||||
import { copyTextToClipboard } from '../utils';
|
||||
|
||||
const InputField = ({
|
||||
inputType,
|
||||
envVariables,
|
||||
setEnvVariables,
|
||||
variables,
|
||||
setVariables,
|
||||
fieldVisibility,
|
||||
setFieldVisibility,
|
||||
...downshiftProps
|
||||
|
@ -51,12 +52,14 @@ const InputField = ({
|
|||
DEFAULT_ROLES: false,
|
||||
PROTECTED_ROLES: false,
|
||||
ALLOWED_ORIGINS: false,
|
||||
roles: false,
|
||||
});
|
||||
const [inputData, setInputData] = React.useState<Record<string, string>>({
|
||||
ROLES: '',
|
||||
DEFAULT_ROLES: '',
|
||||
PROTECTED_ROLES: '',
|
||||
ALLOWED_ORIGINS: '',
|
||||
roles: '',
|
||||
});
|
||||
const updateInputHandler = (
|
||||
type: string,
|
||||
|
@ -65,20 +68,20 @@ const InputField = ({
|
|||
) => {
|
||||
if (operation === ArrayInputOperations.APPEND) {
|
||||
if (inputData[type] !== '') {
|
||||
setEnvVariables({
|
||||
...envVariables,
|
||||
[type]: [...envVariables[type], inputData[type]],
|
||||
setVariables({
|
||||
...variables,
|
||||
[type]: [...variables[type], inputData[type]],
|
||||
});
|
||||
setInputData({ ...inputData, [type]: '' });
|
||||
}
|
||||
setInputFieldVisibility({ ...inputFieldVisibility, [type]: false });
|
||||
}
|
||||
if (operation === ArrayInputOperations.REMOVE) {
|
||||
let updatedEnvVars = envVariables[type].filter(
|
||||
let updatedEnvVars = variables[type].filter(
|
||||
(item: string) => item !== role
|
||||
);
|
||||
setEnvVariables({
|
||||
...envVariables,
|
||||
setVariables({
|
||||
...variables,
|
||||
[type]: updatedEnvVars,
|
||||
});
|
||||
}
|
||||
|
@ -88,14 +91,14 @@ const InputField = ({
|
|||
<InputGroup size="sm">
|
||||
<Input
|
||||
{...props}
|
||||
value={envVariables[inputType]}
|
||||
value={variables[inputType] ? variables[inputType] : ''}
|
||||
onChange={(
|
||||
event: Event & {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
) =>
|
||||
setEnvVariables({
|
||||
...envVariables,
|
||||
setVariables({
|
||||
...variables,
|
||||
[inputType]: event.target.value,
|
||||
})
|
||||
}
|
||||
|
@ -103,7 +106,7 @@ const InputField = ({
|
|||
<InputRightElement
|
||||
children={<FaRegClone color="#bfbfbf" />}
|
||||
cursor="pointer"
|
||||
onClick={() => copyTextToClipboard(envVariables[inputType])}
|
||||
onClick={() => copyTextToClipboard(variables[inputType])}
|
||||
/>
|
||||
</InputGroup>
|
||||
);
|
||||
|
@ -113,14 +116,14 @@ const InputField = ({
|
|||
<InputGroup size="sm">
|
||||
<Input
|
||||
{...props}
|
||||
value={envVariables[inputType]}
|
||||
value={variables[inputType]}
|
||||
onChange={(
|
||||
event: Event & {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
) =>
|
||||
setEnvVariables({
|
||||
...envVariables,
|
||||
setVariables({
|
||||
...variables,
|
||||
[inputType]: event.target.value,
|
||||
})
|
||||
}
|
||||
|
@ -163,7 +166,7 @@ const InputField = ({
|
|||
w="25px"
|
||||
margin="0 1.5%"
|
||||
cursor="pointer"
|
||||
onClick={() => copyTextToClipboard(envVariables[inputType])}
|
||||
onClick={() => copyTextToClipboard(variables[inputType])}
|
||||
>
|
||||
<FaRegClone color="#bfbfbf" />
|
||||
</Center>
|
||||
|
@ -178,13 +181,14 @@ const InputField = ({
|
|||
<Flex
|
||||
border="1px solid #e2e8f0"
|
||||
w="100%"
|
||||
h="45px"
|
||||
wrap="wrap"
|
||||
overflow="scroll"
|
||||
padding="1%"
|
||||
paddingTop="0.5%"
|
||||
overflowX="scroll"
|
||||
overflowY="hidden"
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
{envVariables[inputType].map((role: string, index: number) => (
|
||||
<Box key={index} margin="1" role="group">
|
||||
{variables[inputType].map((role: string, index: number) => (
|
||||
<Box key={index} margin="0.5%" role="group">
|
||||
<Tag
|
||||
size="sm"
|
||||
variant="outline"
|
||||
|
@ -210,10 +214,11 @@ const InputField = ({
|
|||
</Box>
|
||||
))}
|
||||
{inputFieldVisibility[inputType] ? (
|
||||
<Box ml="1.15%">
|
||||
<Box ml="1%" mb="0.75%">
|
||||
<Input
|
||||
type="text"
|
||||
size="xs"
|
||||
minW="150px"
|
||||
placeholder="add a new value"
|
||||
value={inputData[inputType]}
|
||||
onChange={(e: any) => {
|
||||
|
@ -231,7 +236,7 @@ const InputField = ({
|
|||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
margin="1"
|
||||
marginLeft="0.5%"
|
||||
cursor="pointer"
|
||||
onClick={() =>
|
||||
setInputFieldVisibility({
|
||||
|
@ -254,9 +259,10 @@ const InputField = ({
|
|||
);
|
||||
}
|
||||
if (Object.values(SelectInputType).includes(inputType)) {
|
||||
if (inputType === SelectInputType.JWT_TYPE) {
|
||||
return (
|
||||
<Select size="sm" {...props}>
|
||||
{[envVariables[inputType]].map((value: string) => (
|
||||
{[variables[inputType]].map((value: string) => (
|
||||
<option value="value" key={value}>
|
||||
{value}
|
||||
</option>
|
||||
|
@ -264,6 +270,24 @@ const InputField = ({
|
|||
</Select>
|
||||
);
|
||||
}
|
||||
const { options, ...rest } = props;
|
||||
return (
|
||||
<Select
|
||||
size="sm"
|
||||
{...rest}
|
||||
value={variables[inputType] ? variables[inputType] : ''}
|
||||
onChange={(e) =>
|
||||
setVariables({ ...variables, [inputType]: e.target.value })
|
||||
}
|
||||
>
|
||||
{Object.entries(options).map(([key, value]: any) => (
|
||||
<option value={value} key={key}>
|
||||
{key}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
if (Object.values(TextAreaInputType).includes(inputType)) {
|
||||
return (
|
||||
<Textarea
|
||||
|
@ -282,11 +306,11 @@ const InputField = ({
|
|||
<Code h="75%">Off</Code>
|
||||
<Switch
|
||||
size="md"
|
||||
isChecked={envVariables[inputType]}
|
||||
isChecked={variables[inputType]}
|
||||
onChange={() => {
|
||||
setEnvVariables({
|
||||
...envVariables,
|
||||
[inputType]: !envVariables[inputType],
|
||||
setVariables({
|
||||
...variables,
|
||||
[inputType]: !variables[inputType],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
@ -294,6 +318,20 @@ const InputField = ({
|
|||
</Flex>
|
||||
);
|
||||
}
|
||||
if (Object.values(DateInputType).includes(inputType)) {
|
||||
return (
|
||||
<Flex border="1px solid #e2e8f0" w="100%" h="33px" padding="1%">
|
||||
<input
|
||||
type="date"
|
||||
style={{ width: '100%', paddingLeft: '2.5%' }}
|
||||
value={variables[inputType] ? variables[inputType] : ''}
|
||||
onChange={(e) =>
|
||||
setVariables({ ...variables, [inputType]: e.target.value })
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@ export const TextInputType = {
|
|||
DATABASE_NAME: 'DATABASE_NAME',
|
||||
DATABASE_TYPE: 'DATABASE_TYPE',
|
||||
DATABASE_URL: 'DATABASE_URL',
|
||||
GIVEN_NAME: 'given_name',
|
||||
MIDDLE_NAME: 'middle_name',
|
||||
FAMILY_NAME: 'family_name',
|
||||
NICKNAME: 'nickname',
|
||||
PHONE_NUMBER: 'phone_number',
|
||||
PICTURE: 'picture',
|
||||
};
|
||||
|
||||
export const HiddenInputType = {
|
||||
|
@ -33,10 +39,12 @@ export const ArrayInputType = {
|
|||
DEFAULT_ROLES: 'DEFAULT_ROLES',
|
||||
PROTECTED_ROLES: 'PROTECTED_ROLES',
|
||||
ALLOWED_ORIGINS: 'ALLOWED_ORIGINS',
|
||||
USER_ROLES: 'roles',
|
||||
};
|
||||
|
||||
export const SelectInputType = {
|
||||
JWT_TYPE: 'JWT_TYPE',
|
||||
GENDER: 'gender',
|
||||
};
|
||||
|
||||
export const TextAreaInputType = {
|
||||
|
@ -50,6 +58,10 @@ export const SwitchInputType = {
|
|||
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
|
||||
};
|
||||
|
||||
export const DateInputType = {
|
||||
BIRTHDATE: 'birthdate',
|
||||
};
|
||||
|
||||
export const ArrayInputOperations = {
|
||||
APPEND: 'APPEND',
|
||||
REMOVE: 'REMOVE',
|
||||
|
|
|
@ -31,3 +31,13 @@ export const UpdateEnvVariables = `
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UpdateUser = `
|
||||
mutation updateUser(
|
||||
$params: UpdateUserInput!
|
||||
) {
|
||||
_update_user(params: $params) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -42,3 +42,31 @@ export const EnvVariablesQuery = `
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UserDetailsQuery = `
|
||||
query($params: PaginatedInput) {
|
||||
_users(params: $params) {
|
||||
pagination {
|
||||
limit
|
||||
page
|
||||
offset
|
||||
total
|
||||
}
|
||||
users {
|
||||
id
|
||||
email
|
||||
email_verified
|
||||
given_name
|
||||
family_name
|
||||
middle_name
|
||||
nickname
|
||||
gender
|
||||
birthdate
|
||||
phone_number
|
||||
picture
|
||||
roles
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -249,16 +249,16 @@ export default function Environment() {
|
|||
</Center>
|
||||
<Center w="45%" marginRight="1.5%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.GOOGLE_CLIENT_ID}
|
||||
placeholder="Google Client ID"
|
||||
/>
|
||||
</Center>
|
||||
<Center w="45%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.GOOGLE_CLIENT_SECRET}
|
||||
|
@ -277,16 +277,16 @@ export default function Environment() {
|
|||
</Center>
|
||||
<Center w="45%" marginRight="1.5%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.GITHUB_CLIENT_ID}
|
||||
placeholder="Github Client ID"
|
||||
/>
|
||||
</Center>
|
||||
<Center w="45%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.GITHUB_CLIENT_SECRET}
|
||||
|
@ -305,16 +305,16 @@ export default function Environment() {
|
|||
</Center>
|
||||
<Center w="45%" marginRight="1.5%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.FACEBOOK_CLIENT_ID}
|
||||
placeholder="Facebook Client ID"
|
||||
/>
|
||||
</Center>
|
||||
<Center w="45%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.FACEBOOK_CLIENT_SECRET}
|
||||
|
@ -334,8 +334,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={ArrayInputType.ROLES}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -346,8 +346,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={ArrayInputType.DEFAULT_ROLES}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -358,8 +358,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={ArrayInputType.PROTECTED_ROLES}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -378,8 +378,8 @@ export default function Environment() {
|
|||
<Flex w="100%" justifyContent="space-between">
|
||||
<Flex flex="2">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={SelectInputType.JWT_TYPE}
|
||||
isDisabled={true}
|
||||
defaultValue={SelectInputType.JWT_TYPE}
|
||||
|
@ -399,8 +399,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.JWT_SECRET}
|
||||
|
@ -413,8 +413,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.JWT_ROLE_CLAIM}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -431,8 +431,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.REDIS_URL}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -449,8 +449,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.SMTP_HOST}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -461,8 +461,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.SMTP_PORT}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -473,8 +473,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.SMTP_USERNAME}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -485,8 +485,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
inputType={HiddenInputType.SMTP_PASSWORD}
|
||||
|
@ -499,8 +499,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.SENDER_EMAIL}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -517,8 +517,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={ArrayInputType.ALLOWED_ORIGINS}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -535,8 +535,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.ORGANIZATION_NAME}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -547,8 +547,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.ORGANIZATION_LOGO}
|
||||
/>
|
||||
</Center>
|
||||
|
@ -562,8 +562,8 @@ export default function Environment() {
|
|||
<Flex>
|
||||
<Center w="100%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
|
||||
placeholder="Add script here"
|
||||
minH="25vh"
|
||||
|
@ -582,8 +582,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Flex justifyContent="start" w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={SwitchInputType.DISABLE_LOGIN_PAGE}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -594,8 +594,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Flex justifyContent="start" w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={SwitchInputType.DISABLE_EMAIL_VERIFICATION}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -606,8 +606,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Flex justifyContent="start" w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={SwitchInputType.DISABLE_MAGIC_LINK_LOGIN}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -618,8 +618,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Flex justifyContent="start" w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={SwitchInputType.DISABLE_BASIC_AUTHENTICATION}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -647,8 +647,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.DATABASE_NAME}
|
||||
isDisabled={true}
|
||||
/>
|
||||
|
@ -660,8 +660,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.DATABASE_TYPE}
|
||||
isDisabled={true}
|
||||
/>
|
||||
|
@ -673,8 +673,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={TextInputType.DATABASE_URL}
|
||||
isDisabled={true}
|
||||
/>
|
||||
|
@ -743,8 +743,8 @@ export default function Environment() {
|
|||
</Flex>
|
||||
<Center w="70%">
|
||||
<InputField
|
||||
envVariables={envVariables}
|
||||
setEnvVariables={setEnvVariables}
|
||||
variables={envVariables}
|
||||
setVariables={setEnvVariables}
|
||||
inputType={HiddenInputType.ADMIN_SECRET}
|
||||
fieldVisibility={fieldVisibility}
|
||||
setFieldVisibility={setFieldVisibility}
|
||||
|
|
|
@ -1,6 +1,363 @@
|
|||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { useClient } from 'urql';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
IconButton,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Select,
|
||||
Table,
|
||||
Tag,
|
||||
Tbody,
|
||||
Td,
|
||||
Text,
|
||||
TableCaption,
|
||||
Th,
|
||||
Thead,
|
||||
Tooltip,
|
||||
Tr,
|
||||
Button,
|
||||
Center,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
FaAngleLeft,
|
||||
FaAngleRight,
|
||||
FaAngleDoubleLeft,
|
||||
FaAngleDoubleRight,
|
||||
FaExclamationCircle,
|
||||
FaAngleDown,
|
||||
} from 'react-icons/fa';
|
||||
import { UserDetailsQuery } from '../graphql/queries';
|
||||
import { UpdateUser } from '../graphql/mutation';
|
||||
import EditUserModal from '../components/EditUserModal';
|
||||
|
||||
interface paginationPropTypes {
|
||||
limit: number;
|
||||
page: number;
|
||||
offset: number;
|
||||
total: number;
|
||||
maxPages: number;
|
||||
}
|
||||
|
||||
interface userDataTypes {
|
||||
id: string;
|
||||
email: string;
|
||||
email_verified: boolean;
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
middle_name: string;
|
||||
nickname: string;
|
||||
gender: string;
|
||||
birthdate: string;
|
||||
phone_number: string;
|
||||
picture: string;
|
||||
roles: [string];
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||
const { limit, total } = pagination;
|
||||
if (total > 1) {
|
||||
return total % limit === 0
|
||||
? total / limit
|
||||
: parseInt(`${total / limit}`) + 1;
|
||||
} else return 1;
|
||||
};
|
||||
|
||||
const getLimits = (pagination: paginationPropTypes) => {
|
||||
const { total } = pagination;
|
||||
const limits = [5];
|
||||
if (total > 10) {
|
||||
for (let i = 10; i <= total && limits.length <= 10; i += 5) {
|
||||
limits.push(i);
|
||||
}
|
||||
}
|
||||
return limits;
|
||||
};
|
||||
|
||||
export default function Users() {
|
||||
return <Box>Welcome to Users Page</Box>;
|
||||
const client = useClient();
|
||||
const toast = useToast();
|
||||
const [paginationProps, setPaginationProps] =
|
||||
React.useState<paginationPropTypes>({
|
||||
limit: 5,
|
||||
page: 1,
|
||||
offset: 0,
|
||||
total: 0,
|
||||
maxPages: 1,
|
||||
});
|
||||
const [userList, setUserList] = React.useState<userDataTypes[]>([]);
|
||||
const updateUserList = async () => {
|
||||
const { data } = await client
|
||||
.query(UserDetailsQuery, {
|
||||
params: {
|
||||
pagination: {
|
||||
limit: paginationProps.limit,
|
||||
page: paginationProps.page,
|
||||
},
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
if (data?._users) {
|
||||
const { pagination, users } = data._users;
|
||||
const maxPages = getMaxPages(pagination);
|
||||
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||
setUserList(users);
|
||||
}
|
||||
};
|
||||
React.useEffect(() => {
|
||||
updateUserList();
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
updateUserList();
|
||||
}, [paginationProps.page, paginationProps.limit]);
|
||||
|
||||
const paginationHandler = (value: Record<string, number>) => {
|
||||
setPaginationProps({ ...paginationProps, ...value });
|
||||
};
|
||||
|
||||
const userVerificationHandler = async (user: userDataTypes) => {
|
||||
const { id, email } = user;
|
||||
const res = await client
|
||||
.mutation(UpdateUser, {
|
||||
params: {
|
||||
id,
|
||||
email,
|
||||
email_verified: true,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
if (res.error) {
|
||||
toast({
|
||||
title: 'User verification failed',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
} else if (res.data?._update_user?.id) {
|
||||
toast({
|
||||
title: 'User verification successful',
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
position: 'bottom-right',
|
||||
});
|
||||
}
|
||||
updateUserList();
|
||||
};
|
||||
return (
|
||||
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||
<Text fontSize="md" fontWeight="bold">
|
||||
Users
|
||||
</Text>
|
||||
</Flex>
|
||||
{userList.length > 0 ? (
|
||||
<Table variant="simple">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Email</Th>
|
||||
<Th>Created At</Th>
|
||||
<Th>Verified</Th>
|
||||
<Th>Actions</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{userList.map((user: userDataTypes) => {
|
||||
const { email_verified, created_at, ...rest }: any = user;
|
||||
return (
|
||||
<Tr key={user.id} style={{ fontSize: 14 }}>
|
||||
<Td>{user.email}</Td>
|
||||
<Td>{dayjs(user.created_at).format('MMM DD, YYYY')}</Td>
|
||||
<Td>
|
||||
<Tag
|
||||
size="sm"
|
||||
variant="outline"
|
||||
colorScheme={user.email_verified ? 'green' : 'yellow'}
|
||||
>
|
||||
{user.email_verified.toString()}
|
||||
</Tag>
|
||||
</Td>
|
||||
<Td>
|
||||
<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
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
{paginationProps.maxPages > 1 && (
|
||||
<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({
|
||||
page: 1,
|
||||
limit: parseInt(e.target.value),
|
||||
})
|
||||
}
|
||||
>
|
||||
{getLimits(paginationProps).map((pageSize) => (
|
||||
<option key={pageSize} value={pageSize}>
|
||||
Show {pageSize}
|
||||
</option>
|
||||
))}
|
||||
</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>
|
||||
</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
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,11 @@ export const getObjectDiff = (obj1: any, obj2: any) => {
|
|||
const diff = Object.keys(obj1).reduce((result, key) => {
|
||||
if (!obj2.hasOwnProperty(key)) {
|
||||
result.push(key);
|
||||
} else if (_.isEqual(obj1[key], obj2[key])) {
|
||||
} else if (
|
||||
_.isEqual(obj1[key], obj2[key]) ||
|
||||
(obj1[key] === null && obj2[key] === '') ||
|
||||
(obj1[key] === [] && obj2[key] === null)
|
||||
) {
|
||||
const resultKeyIndex = result.indexOf(key);
|
||||
result.splice(resultKeyIndex, 1);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user